You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2021/06/22 15:01:46 UTC
[sis] 01/01: Updates for change in `Filter` interfaces: -
`FilterVisitor` and `ExpressionVisitor` removed - `IncludeFilter` and
`ExcludeFilter` removed from public API - `NilExpression` removed -
`Filter` as a sub-type of `java.util.Predicate` - `Expression` as a
sub-type of `java.util.Function`
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch refactor/filters
in repository https://gitbox.apache.org/repos/asf/sis.git
commit b94189b7a8e87acc35d62fbe8c70713704789bd6
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Mar 30 18:29:44 2021 +0200
Updates for change in `Filter` interfaces:
- `FilterVisitor` and `ExpressionVisitor` removed
- `IncludeFilter` and `ExcludeFilter` removed from public API
- `NilExpression` removed
- `Filter` as a sub-type of `java.util.Predicate`
- `Expression` as a sub-type of `java.util.Function`
Implementation changes;
- Moved JTSMapping method to a `Wrapper.toGeometryType(…)` method.
- Remove some classes and methods no longer used.
- Refactor SQLMM test in a more implementation neutral (JTS versus ESRI) way.
- Initial `Optimization` support.
https://github.com/opengeospatial/geoapi/issues/32
---
.../src/main/java/org/apache/sis/cql/CQL.java | 607 +++++----
.../org/apache/sis/cql/FilterToCQLVisitor.java | 870 ++++---------
.../test/java/org/apache/sis/cql/CQLTestCase.java | 22 +-
.../test/java/org/apache/sis/cql/CQLTestSuite.java | 4 +-
.../org/apache/sis/cql/ExpressionReadingTest.java | 127 +-
.../org/apache/sis/cql/ExpressionWritingTest.java | 71 +-
.../java/org/apache/sis/cql/FilterReadingTest.java | 727 +++--------
.../java/org/apache/sis/cql/FilterWritingTest.java | 111 +-
.../org/apache/sis/feature/EnvelopeOperation.java | 4 +-
.../java/org/apache/sis/feature/FeatureFormat.java | 3 +-
.../org/apache/sis/filter/ArithmeticFunction.java | 220 ++--
.../java/org/apache/sis/filter/BinaryFunction.java | 158 +--
.../apache/sis/filter/BinaryGeometryFilter.java | 212 +++
.../java/org/apache/sis/filter/CRSMatching.java | 167 ---
.../java/org/apache/sis/filter/Capabilities.java | 531 ++------
.../org/apache/sis/filter/ComparisonFunction.java | 258 ++--
.../org/apache/sis/filter/ConvertFunction.java | 197 +++
.../java/org/apache/sis/filter/DefaultBBOX.java | 264 ----
.../apache/sis/filter/DefaultFilterFactory.java | 1371 ++++++++++----------
.../java/org/apache/sis/filter/DefaultLike.java | 284 ++--
.../org/apache/sis/filter/DefaultObjectId.java | 111 --
.../java/org/apache/sis/filter/DefaultSortBy.java | 17 +-
.../java/org/apache/sis/filter/DistanceFilter.java | 183 +++
.../org/apache/sis/filter/FilterByIdentifier.java | 97 +-
.../sis/filter/InvalidExpressionException.java | 93 --
.../java/org/apache/sis/filter/LeafExpression.java | 210 ++-
.../org/apache/sis/filter/LogicalFunction.java | 257 +++-
.../java/org/apache/sis/filter/Optimization.java | 297 +++++
.../java/org/apache/sis/filter/PropertyValue.java | 228 ++++
.../org/apache/sis/filter/SpatialFunction.java | 661 ++--------
.../org/apache/sis/filter/TemporalFunction.java | 404 +++---
.../java/org/apache/sis/filter/UnaryFunction.java | 144 +-
.../java/org/apache/sis/filter/package-info.java | 47 +-
.../sis/internal/feature/FeatureExpression.java | 45 +-
.../apache/sis/internal/feature/Geometries.java | 184 ++-
.../apache/sis/internal/feature/GeometryType.java | 83 ++
.../sis/internal/feature/GeometryWithCRS.java | 19 +
.../sis/internal/feature/GeometryWrapper.java | 338 ++++-
.../org/apache/sis/internal/feature/Resources.java | 15 +-
.../sis/internal/feature/Resources.properties | 3 +-
.../sis/internal/feature/Resources_fr.properties | 2 +-
.../internal/feature/SpatialOperationContext.java | 454 +++++++
.../apache/sis/internal/feature/esri/Factory.java | 44 +-
.../apache/sis/internal/feature/esri/Wrapper.java | 96 +-
.../apache/sis/internal/feature/j2d/Factory.java | 49 +-
.../sis/internal/feature/j2d/PointWrapper.java | 28 +-
.../apache/sis/internal/feature/j2d/Wrapper.java | 34 +-
.../apache/sis/internal/feature/jts/Factory.java | 179 ++-
.../sis/internal/feature/jts/FilteringContext.java | 89 ++
.../org/apache/sis/internal/feature/jts/JTS.java | 85 +-
.../sis/internal/feature/jts/JTSMapping.java | 373 ------
.../apache/sis/internal/feature/jts/Wrapper.java | 443 ++++++-
.../sis/internal/filter/FilterGeometryUtils.java | 278 ----
.../apache/sis/internal/filter/FunctionNames.java | 92 ++
.../{feature => filter}/FunctionRegister.java | 18 +-
.../sis/internal/filter/GeometryConverter.java | 143 ++
.../apache/sis/internal/filter/NamedFunction.java | 213 ---
.../java/org/apache/sis/internal/filter/Node.java | 141 +-
.../org/apache/sis/internal/filter/Visitor.java | 301 +++++
.../sqlmm/AbstractAccessorSpatialFunction.java | 64 -
.../sqlmm/AbstractBinarySpatialFunction.java | 85 --
.../filter/sqlmm/AbstractGeomConstructor.java | 104 --
.../filter/sqlmm/AbstractSpatialFunction.java | 122 --
.../internal/filter/sqlmm/FunctionWithSRID.java | 222 ++++
.../internal/filter/sqlmm/GeometryConstructor.java | 141 ++
.../sis/internal/filter/sqlmm/GeometryParser.java | 132 ++
.../sis/internal/filter/sqlmm/OneGeometry.java | 154 +++
.../apache/sis/internal/filter/sqlmm/Registry.java | 136 ++
.../apache/sis/internal/filter/sqlmm/SQLMM.java | 1067 +++++++++++----
.../apache/sis/internal/filter/sqlmm/ST_Area.java | 69 -
.../sis/internal/filter/sqlmm/ST_AsBinary.java | 68 -
.../apache/sis/internal/filter/sqlmm/ST_AsGML.java | 68 -
.../sis/internal/filter/sqlmm/ST_AsText.java | 70 -
.../sis/internal/filter/sqlmm/ST_Boundary.java | 72 -
.../sis/internal/filter/sqlmm/ST_Buffer.java | 118 --
.../sis/internal/filter/sqlmm/ST_Centroid.java | 116 --
.../sis/internal/filter/sqlmm/ST_Contains.java | 63 -
.../sis/internal/filter/sqlmm/ST_ConvexHull.java | 72 -
.../sis/internal/filter/sqlmm/ST_CoordDim.java | 72 -
.../sis/internal/filter/sqlmm/ST_Crosses.java | 63 -
.../sis/internal/filter/sqlmm/ST_Difference.java | 67 -
.../sis/internal/filter/sqlmm/ST_Dimension.java | 67 -
.../sis/internal/filter/sqlmm/ST_Disjoint.java | 63 -
.../sis/internal/filter/sqlmm/ST_Distance.java | 63 -
.../sis/internal/filter/sqlmm/ST_EndPoint.java | 73 --
.../sis/internal/filter/sqlmm/ST_Envelope.java | 199 ---
.../sis/internal/filter/sqlmm/ST_Equals.java | 63 -
.../internal/filter/sqlmm/ST_ExplicitPoint.java | 75 --
.../sis/internal/filter/sqlmm/ST_ExteriorRing.java | 71 -
.../sis/internal/filter/sqlmm/ST_FromBinary.java | 86 ++
.../sis/internal/filter/sqlmm/ST_FromText.java | 82 ++
.../internal/filter/sqlmm/ST_GeomCollection.java | 52 -
.../sis/internal/filter/sqlmm/ST_GeometryN.java | 83 --
.../sis/internal/filter/sqlmm/ST_GeometryType.java | 94 --
.../internal/filter/sqlmm/ST_InteriorRingN.java | 72 -
.../sis/internal/filter/sqlmm/ST_Intersection.java | 67 -
.../sis/internal/filter/sqlmm/ST_Intersects.java | 63 -
.../apache/sis/internal/filter/sqlmm/ST_Is3D.java | 72 -
.../sis/internal/filter/sqlmm/ST_IsClosed.java | 69 -
.../sis/internal/filter/sqlmm/ST_IsEmpty.java | 67 -
.../sis/internal/filter/sqlmm/ST_IsRing.java | 69 -
.../sis/internal/filter/sqlmm/ST_IsSimple.java | 68 -
.../sis/internal/filter/sqlmm/ST_IsValid.java | 67 -
.../sis/internal/filter/sqlmm/ST_Length.java | 69 -
.../sis/internal/filter/sqlmm/ST_LineString.java | 188 ---
.../sis/internal/filter/sqlmm/ST_MultiPolygon.java | 52 -
.../internal/filter/sqlmm/ST_NumGeometries.java | 67 -
.../internal/filter/sqlmm/ST_NumInteriorRings.java | 69 -
.../sis/internal/filter/sqlmm/ST_NumPoints.java | 67 -
.../sis/internal/filter/sqlmm/ST_Overlaps.java | 63 -
.../sis/internal/filter/sqlmm/ST_Perimeter.java | 71 -
.../apache/sis/internal/filter/sqlmm/ST_Point.java | 316 +++--
.../sis/internal/filter/sqlmm/ST_PointN.java | 83 --
.../internal/filter/sqlmm/ST_PointOnSurface.java | 75 --
.../sis/internal/filter/sqlmm/ST_Polygon.java | 60 -
.../sis/internal/filter/sqlmm/ST_Relate.java | 74 --
.../apache/sis/internal/filter/sqlmm/ST_SRID.java | 67 -
.../sis/internal/filter/sqlmm/ST_Simplify.java | 113 --
.../filter/sqlmm/ST_SimplifyPreserveTopology.java | 113 --
.../sis/internal/filter/sqlmm/ST_StartPoint.java | 75 --
.../internal/filter/sqlmm/ST_SymDifference.java | 67 -
.../sis/internal/filter/sqlmm/ST_ToGeomColl.java | 71 -
.../sis/internal/filter/sqlmm/ST_ToLineString.java | 71 -
.../sis/internal/filter/sqlmm/ST_ToMultiLine.java | 71 -
.../sis/internal/filter/sqlmm/ST_ToMultiPoint.java | 71 -
.../internal/filter/sqlmm/ST_ToMultiPolygon.java | 71 -
.../sis/internal/filter/sqlmm/ST_ToPoint.java | 71 -
.../sis/internal/filter/sqlmm/ST_ToPolygon.java | 71 -
.../sis/internal/filter/sqlmm/ST_Touches.java | 63 -
.../sis/internal/filter/sqlmm/ST_Transform.java | 174 +--
.../apache/sis/internal/filter/sqlmm/ST_Union.java | 67 -
.../sis/internal/filter/sqlmm/ST_Within.java | 63 -
.../org/apache/sis/internal/filter/sqlmm/ST_X.java | 67 -
.../sis/internal/filter/sqlmm/ST_XFromBinary.java | 385 ------
.../sis/internal/filter/sqlmm/ST_XFromGML.java | 298 -----
.../sis/internal/filter/sqlmm/ST_XFromText.java | 385 ------
.../org/apache/sis/internal/filter/sqlmm/ST_Y.java | 68 -
.../org/apache/sis/internal/filter/sqlmm/ST_Z.java | 68 -
.../sis/internal/filter/sqlmm/SpatialFunction.java | 228 ++++
.../sis/internal/filter/sqlmm/TwoGeometries.java | 161 +++
.../{ST_MultiLineString.java => package-info.java} | 48 +-
.../apache/sis/filter/ArithmeticFunctionTest.java | 32 +-
.../org/apache/sis/filter/BetweenFunctionTest.java | 91 --
.../org/apache/sis/filter/CapabilitiesTest.java | 44 +-
.../apache/sis/filter/ComparisonFunctionTest.java | 80 +-
.../org/apache/sis/filter/DefaultLikeTest.java | 83 ++
.../org/apache/sis/filter/DefaultObjectIdTest.java | 100 --
.../apache/sis/filter/FilterByIdentifierTest.java | 83 +-
.../org/apache/sis/filter/LeafExpressionTest.java | 116 +-
.../org/apache/sis/filter/LikeFunctionTest.java | 66 -
.../org/apache/sis/filter/LogicalFunctionTest.java | 193 +--
.../java/org/apache/sis/filter/PeriodLiteral.java | 26 +-
.../org/apache/sis/filter/SpatialFunctionTest.java | 348 -----
.../org/apache/sis/filter/SpatialTestCase.java | 334 +++++
...bilitiesTest.java => SpatialUsingJTS_Test.java} | 33 +-
.../apache/sis/filter/TemporalFunctionTest.java | 63 +-
.../org/apache/sis/filter/UnaryFunctionTest.java | 76 --
.../sis/internal/feature/GeometriesTestCase.java | 10 +-
.../sis/internal/filter/FunctionNamesTest.java | 149 +++
.../internal/filter/sqlmm/RegistryTestCase.java | 473 +++++++
.../filter/sqlmm/RegistryUsingJTS_Test.java} | 36 +-
.../sis/internal/filter/sqlmm/SQLMMTest.java | 403 +-----
.../apache/sis/test/suite/FeatureTestSuite.java | 11 +-
.../src/test/resources/io/wkt/extra_ESRI_CRS.txt | 18 -
.../main/java/org/apache/sis/util/iso/Names.java | 2 +
.../sis/internal/map/ExceptionPresentation.java | 8 +-
.../sis/internal/map/PropertyNameCollector.java | 35 +-
.../org/apache/sis/internal/map/SEPortrayer.java | 366 +++---
.../apache/sis/internal/map/SymbologyVisitor.java | 530 ++++----
.../sis/internal/map/MockFeatureTypeStyle.java | 21 +-
.../sis/internal/map/MockLineSymbolizer.java | 9 +-
.../apache/sis/internal/map/SEPortrayerTest.java | 76 +-
.../internal/referencing/ReferencingUtilities.java | 14 +
.../apache/sis/referencing/GeodeticCalculator.java | 2 +-
.../apache/sis/referencing/crs/HardCodedCRS.java | 16 +
.../sis/referencing/datum/GeodeticDatumMock.java | 8 +-
.../operation/HardCodedConversions.java | 26 +
.../java/org/apache/sis/internal/jdk9/JDK9.java | 8 +
.../java/org/apache/sis/test/LoggingWatcher.java | 1 +
.../sis/internal/sql/feature/ANSIInterpreter.java | 701 ++++------
.../sis/internal/sql/feature/FeatureAdapter.java | 18 +-
.../apache/sis/internal/sql/feature/Features.java | 78 +-
.../internal/sql/feature/PostGISInterpreter.java | 38 +-
.../sis/internal/sql/feature/QueryFeatureSet.java | 6 +-
.../sis/internal/sql/feature/SQLQueryAdapter.java | 52 +-
.../sis/internal/sql/feature/SubsetAdapter.java | 15 +-
.../sis/internal/sql/feature/TableSubset.java | 6 +-
.../sql/feature/FilterInterpreterTest.java | 36 +-
.../org/apache/sis/storage/sql/SQLStoreTest.java | 43 +-
.../sis/internal/storage/JoinFeatureSet.java | 38 +-
.../org/apache/sis/internal/storage/Resources.java | 5 +
.../sis/internal/storage/Resources.properties | 1 +
.../sis/internal/storage/Resources_fr.properties | 1 +
.../sis/internal/storage/query/FeatureSubset.java | 15 +-
.../sis/internal/storage/query/SimpleQuery.java | 58 +-
.../internal/storage/query/SortByComparator.java | 16 +-
.../main/java/org/apache/sis/storage/Query.java | 2 +
.../sis/internal/storage/JoinFeatureSetTest.java | 14 +-
.../internal/storage/query/SimpleQueryTest.java | 24 +-
.../apache/sis/internal/storage/gpx/Writer.java | 4 +-
200 files changed, 11626 insertions(+), 15332 deletions(-)
diff --git a/core/sis-cql/src/main/java/org/apache/sis/cql/CQL.java b/core/sis-cql/src/main/java/org/apache/sis/cql/CQL.java
index 2c543db..47bf248 100644
--- a/core/sis-cql/src/main/java/org/apache/sis/cql/CQL.java
+++ b/core/sis-cql/src/main/java/org/apache/sis/cql/CQL.java
@@ -16,12 +16,18 @@
*/
package org.apache.sis.cql;
-import java.time.temporal.TemporalAccessor;
-import java.util.ArrayList;
import java.util.List;
+import java.util.ArrayList;
+import java.time.temporal.TemporalAccessor;
+import javax.measure.Unit;
+import javax.measure.quantity.Length;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
+import org.apache.sis.measure.Units;
+import org.apache.sis.measure.Quantities;
import org.apache.sis.filter.DefaultFilterFactory;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.referencing.CRS;
import org.apache.sis.internal.cql.AntlrCQL;
import org.apache.sis.internal.cql.CQLParser.CoordinateContext;
import org.apache.sis.internal.cql.CQLParser.CoordinateSerieContext;
@@ -43,12 +49,14 @@ import org.locationtech.jts.geom.GeometryFactory;
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.*;
@@ -56,8 +64,8 @@ import static org.apache.sis.internal.cql.CQLParser.*;
/**
*
* @author Johann Sorel (Geomatys)
- * @version 1.0
- * @since 1.0
+ * @version 1.1
+ * @since 1.1
* @module
*/
public final class CQL {
@@ -67,80 +75,71 @@ public final class CQL {
private CQL() {
}
- public static Expression parseExpression(String cql) throws CQLException {
+ public static Expression<Feature,?> parseExpression(String cql) throws CQLException {
return parseExpression(cql, null);
}
- public static Expression parseExpression(String cql, FilterFactory2 factory) throws CQLException {
+ public static Expression<Feature,?> parseExpression(String cql, FilterFactory<Feature,?,?> factory) throws CQLException {
final Object obj = AntlrCQL.compileExpression(cql);
- ParseTree tree = null;
- Expression result = null;
+ Expression<Feature,?> result = null;
if (obj instanceof ExpressionContext) {
- tree = (ParseTree) obj;
+ ParseTree tree = (ParseTree) obj;
if (factory == null) {
- factory = new DefaultFilterFactory();
+ factory = DefaultFilterFactory.forFeatures();
}
result = convertExpression(tree, factory);
}
return result;
}
- public static Filter parseFilter(String cql) throws CQLException {
+ public static Filter<? super Feature> parseFilter(String cql) throws CQLException {
return parseFilter(cql, null);
}
- public static Filter parseFilter(String cql, FilterFactory2 factory) throws CQLException {
+ public static Filter<? super Feature> parseFilter(String cql, FilterFactory<Feature,Object,Object> factory) throws CQLException {
cql = cql.trim();
// Bypass parsing for inclusive filter.
if (cql.isEmpty() || "*".equals(cql)) {
- return Filter.INCLUDE;
+ return Filter.include();
}
-
final Object obj = AntlrCQL.compileFilter(cql);
-
- ParseTree tree = null;
- Filter result = null;
+ Filter<? super Feature> result = null;
if (obj instanceof FilterContext) {
- tree = (FilterContext) obj;
+ ParseTree tree = (ParseTree) obj;
if (factory == null) {
- factory = new DefaultFilterFactory();
+ factory = DefaultFilterFactory.forFeatures();
}
result = convertFilter(tree, factory);
}
return result;
}
- public static String write(Filter filter) {
- if (filter == null) {
- return "";
- }
+ public static String write(final Filter<Feature> filter) {
final StringBuilder sb = new StringBuilder();
- filter.accept(FilterToCQLVisitor.INSTANCE, sb);
+ FilterToCQLVisitor.INSTANCE.visit(filter, sb);
return sb.toString();
}
- public static String write(Expression exp) {
- if (exp == null) {
- return "";
- }
+ public static String write(final Expression<Feature,?> exp) {
final StringBuilder sb = new StringBuilder();
- exp.accept(FilterToCQLVisitor.INSTANCE, sb);
+ FilterToCQLVisitor.INSTANCE.visit(exp, sb);
return sb.toString();
}
/**
* Convert the given tree in an Expression.
*/
- private static Expression convertExpression(ParseTree tree, FilterFactory2 ff) throws CQLException {
+ private static Expression<Feature,?> convertExpression(ParseTree tree, FilterFactory<Feature,?,?> ff) throws CQLException {
if (tree instanceof ExpressionContext) {
//: expression MULT expression
//| expression UNARY expression
//| expressionTerm
if (tree.getChildCount() == 3) {
final String operand = tree.getChild(1).getText();
- final Expression left = convertExpression((ParseTree) tree.getChild(0), ff);
- final Expression right = convertExpression((ParseTree) tree.getChild(2), ff);
+ // TODO: unsafe cast.
+ final Expression<? super Feature, ? extends Number> left = (Expression<? super Feature, ? extends Number>) convertExpression(tree.getChild(0), ff);
+ final Expression<? super Feature, ? extends Number> right = (Expression<? super Feature, ? extends Number>) convertExpression(tree.getChild(2), ff);
if ("*".equals(operand)) {
return ff.multiply(left, right);
} else if ("/".equals(operand)) {
@@ -180,12 +179,10 @@ public final class CQL {
if (exp.getChildCount() == 1) {
return convertExpression(tree.getChild(0), ff);
}
-
// LPAREN expression RPAREN
if (exp.expression() != null) {
return convertExpression(exp.expression(), ff);
}
-
// NAME (LPAREN expressionFctParam? RPAREN)?
if (exp.NAME() != null) {
final String name = exp.NAME().getText();
@@ -194,10 +191,9 @@ public final class CQL {
//handle as property name
return ff.property(name);
}
-
// Handle as a function.
final List<ExpressionContext> params = prm.expression();
- final List<Expression> exps = new ArrayList<Expression>();
+ final List<Expression<Feature,?>> exps = new ArrayList<>();
for (int i = 0, n = params.size(); i < n; i++) {
exps.add(convertExpression(params.get(i), ff));
}
@@ -213,28 +209,26 @@ public final class CQL {
return convertExpression(tree.getChild(0), ff);
} else if (tree instanceof TerminalNode) {
final TerminalNode exp = (TerminalNode) tree;
- final int type = exp.getSymbol().getType();
- if (PROPERTY_NAME == type) {
- //strip start and end "
- final String text = tree.getText();
- return ff.property(text.substring(1, text.length() - 1));
- } else if (NAME == type) {
- return ff.property(tree.getText());
- } else if (INT == type) {
- return ff.literal(Integer.valueOf(tree.getText()));
- } else if (FLOAT == type) {
- return ff.literal(Double.valueOf(tree.getText()));
- } else if (DATE == type) {
- TemporalAccessor ta = StandardDateFormat.FORMAT.parse(tree.getText());
- return ff.literal(ta);
- } else if (DURATION_P == type || DURATION_T == type) {
- // TODO
- //return ff.literal(TemporalUtilities.getTimeInMillis(tree.getText()));
- } else if (TEXT == type) {
- //strip start and end '
- String text = tree.getText();
- text = text.replaceAll("\\\\'", "'");
- return ff.literal(text.substring(1, text.length() - 1));
+ switch (exp.getSymbol().getType()) {
+ case PROPERTY_NAME: {
+ // strip start and end "
+ final String text = tree.getText();
+ return ff.property(text.substring(1, text.length() - 1));
+ }
+ case NAME: return ff.property(tree.getText());
+ case INT: return ff.literal(Integer.valueOf(tree.getText()));
+ case FLOAT: return ff.literal(Double.valueOf(tree.getText()));
+ case DATE: {
+ TemporalAccessor ta = StandardDateFormat.FORMAT.parse(tree.getText());
+ return ff.literal(ta);
+ // TODO! return ff.literal(TemporalUtilities.getTimeInMillis(tree.getText()));
+ }
+ case TEXT: {
+ // strip start and end '
+ String text = tree.getText();
+ text = text.replaceAll("\\\\'", "'");
+ return ff.literal(text.substring(1, text.length() - 1));
+ }
}
} else if (tree instanceof ExpressionGeometryContext) {
//: POINT ( EMPTY | coordinateSerie )
@@ -246,133 +240,140 @@ public final class CQL {
//| GEOMETRYCOLLECTION ( EMPTY | (LPAREN expressionGeometry (COMMA expressionGeometry)* RPAREN) )
//| ENVELOPE ( EMPTY | (LPAREN expressionUnary COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary RPAREN) )
final ExpressionGeometryContext exp = (ExpressionGeometryContext) tree;
- final int type = ((TerminalNode) exp.getChild(0)).getSymbol().getType();
-
- if (POINT == type) {
- final ParseTree st = (ParseTree) tree.getChild(1);
- final CoordinateSequence cs;
- if (isEmptyToken(st)) {
- cs = GF.getCoordinateSequenceFactory().create(new Coordinate[0]);
- } else {
- cs = parseSequence(st);
+ switch (((TerminalNode) exp.getChild(0)).getSymbol().getType()) {
+ case POINT: {
+ final ParseTree st = tree.getChild(1);
+ final CoordinateSequence cs;
+ if (isEmptyToken(st)) {
+ cs = GF.getCoordinateSequenceFactory().create(new Coordinate[0]);
+ } else {
+ cs = parseSequence(st);
+ }
+ final Geometry geom = GF.createPoint(cs);
+ return ff.literal(geom);
}
- final Geometry geom = GF.createPoint(cs);
- return ff.literal(geom);
- } else if (LINESTRING == type) {
- final ParseTree st = (ParseTree) tree.getChild(1);
- final CoordinateSequence cs;
- if (isEmptyToken(st)) {
- cs = GF.getCoordinateSequenceFactory().create(new Coordinate[0]);
- } else {
- cs = parseSequence(st);
+ case LINESTRING: {
+ final ParseTree st = tree.getChild(1);
+ final CoordinateSequence cs;
+ if (isEmptyToken(st)) {
+ cs = GF.getCoordinateSequenceFactory().create(new Coordinate[0]);
+ } else {
+ cs = parseSequence(st);
+ }
+ final Geometry geom = GF.createLineString(cs);
+ return ff.literal(geom);
}
- final Geometry geom = GF.createLineString(cs);
- return ff.literal(geom);
- } else if (POLYGON == type) {
- final ParseTree st = (ParseTree) tree.getChild(1);
- final Geometry geom;
- if (isEmptyToken(st)) {
- geom = GF.createPolygon(GF.createLinearRing(new Coordinate[0]), new LinearRing[0]);
- } else {
- final CoordinateSeriesContext series = (CoordinateSeriesContext) st;
- final List<CoordinateSerieContext> subs = series.coordinateSerie();
- final LinearRing contour = GF.createLinearRing(parseSequence(subs.get(0)));
- final int n = subs.size();
- final LinearRing[] holes = new LinearRing[n - 1];
- for (int i = 1; i < n; i++) {
- holes[i - 1] = GF.createLinearRing(parseSequence(subs.get(i)));
+ case POLYGON: {
+ final ParseTree st = tree.getChild(1);
+ final Geometry geom;
+ if (isEmptyToken(st)) {
+ geom = GF.createPolygon(GF.createLinearRing(new Coordinate[0]), new LinearRing[0]);
+ } else {
+ final CoordinateSeriesContext series = (CoordinateSeriesContext) st;
+ final List<CoordinateSerieContext> subs = series.coordinateSerie();
+ final LinearRing contour = GF.createLinearRing(parseSequence(subs.get(0)));
+ final int n = subs.size();
+ final LinearRing[] holes = new LinearRing[n - 1];
+ for (int i = 1; i < n; i++) {
+ holes[i - 1] = GF.createLinearRing(parseSequence(subs.get(i)));
+ }
+ geom = GF.createPolygon(contour, holes);
}
- geom = GF.createPolygon(contour, holes);
+ return ff.literal(geom);
}
- return ff.literal(geom);
- } else if (MPOINT == type) {
- final ParseTree st = (ParseTree) tree.getChild(1);
- final CoordinateSequence cs;
- if (isEmptyToken(st)) {
- cs = GF.getCoordinateSequenceFactory().create(new Coordinate[0]);
- } else {
- cs = parseSequence(st);
+ case MPOINT: {
+ final ParseTree st = tree.getChild(1);
+ final CoordinateSequence cs;
+ if (isEmptyToken(st)) {
+ cs = GF.getCoordinateSequenceFactory().create(new Coordinate[0]);
+ } else {
+ cs = parseSequence(st);
+ }
+ final Geometry geom = GF.createMultiPoint(cs);
+ return ff.literal(geom);
}
- final Geometry geom = GF.createMultiPoint(cs);
- return ff.literal(geom);
- } else if (MLINESTRING == type) {
- final ParseTree st = (ParseTree) tree.getChild(1);
- final Geometry geom;
- if (isEmptyToken(st)) {
- geom = GF.createMultiLineString(new LineString[0]);
- } else {
- final CoordinateSeriesContext series = (CoordinateSeriesContext) st;
- final List<CoordinateSerieContext> subs = series.coordinateSerie();
- final int n = subs.size();
- final LineString[] strings = new LineString[n];
- for (int i = 0; i < n; i++) {
- strings[i] = GF.createLineString(parseSequence(subs.get(i)));
+ case MLINESTRING: {
+ final ParseTree st = tree.getChild(1);
+ final Geometry geom;
+ if (isEmptyToken(st)) {
+ geom = GF.createMultiLineString(new LineString[0]);
+ } else {
+ final CoordinateSeriesContext series = (CoordinateSeriesContext) st;
+ final List<CoordinateSerieContext> subs = series.coordinateSerie();
+ final int n = subs.size();
+ final LineString[] strings = new LineString[n];
+ for (int i = 0; i < n; i++) {
+ strings[i] = GF.createLineString(parseSequence(subs.get(i)));
+ }
+ geom = GF.createMultiLineString(strings);
}
- geom = GF.createMultiLineString(strings);
+ return ff.literal(geom);
}
- return ff.literal(geom);
- } else if (MPOLYGON == type) {
- final ParseTree st = (ParseTree) tree.getChild(1);
- final Geometry geom;
- if (isEmptyToken(st)) {
- geom = GF.createMultiPolygon(new Polygon[0]);
- } else {
- final List<CoordinateSeriesContext> eles = exp.coordinateSeries();
- final int n = eles.size();
- final Polygon[] polygons = new Polygon[n];
- for (int i = 0; i < n; i++) {
- final CoordinateSeriesContext polyTree = eles.get(i);
- final List<CoordinateSerieContext> subs = polyTree.coordinateSerie();
- final LinearRing contour = GF.createLinearRing(parseSequence(subs.get(0)));
- final int hn = subs.size();
- final LinearRing[] holes = new LinearRing[hn - 1];
- for (int j = 1; j < hn; j++) {
- holes[j - 1] = GF.createLinearRing(parseSequence(subs.get(j)));
+ case MPOLYGON: {
+ final ParseTree st = tree.getChild(1);
+ final Geometry geom;
+ if (isEmptyToken(st)) {
+ geom = GF.createMultiPolygon(new Polygon[0]);
+ } else {
+ final List<CoordinateSeriesContext> eles = exp.coordinateSeries();
+ final int n = eles.size();
+ final Polygon[] polygons = new Polygon[n];
+ for (int i=0; i<n; i++) {
+ final CoordinateSeriesContext polyTree = eles.get(i);
+ final List<CoordinateSerieContext> subs = polyTree.coordinateSerie();
+ final LinearRing contour = GF.createLinearRing(parseSequence(subs.get(0)));
+ final int hn = subs.size();
+ final LinearRing[] holes = new LinearRing[hn - 1];
+ for (int j=1; j<hn; j++) {
+ holes[j-1] = GF.createLinearRing(parseSequence(subs.get(j)));
+ }
+ final Polygon poly = GF.createPolygon(contour, holes);
+ polygons[i] = poly;
}
- final Polygon poly = GF.createPolygon(contour, holes);
- polygons[i] = poly;
+ geom = GF.createMultiPolygon(polygons);
}
- geom = GF.createMultiPolygon(polygons);
+ return ff.literal(geom);
}
- return ff.literal(geom);
- } else if (GEOMETRYCOLLECTION == type) {
- final ParseTree st = (ParseTree) tree.getChild(1);
- final Geometry geom;
- if (isEmptyToken(st)) {
- geom = GF.createGeometryCollection(new Geometry[0]);
- } else {
- final List<ExpressionGeometryContext> eles = exp.expressionGeometry();
- final int n = eles.size();
- final Geometry[] subs = new Geometry[n];
- for (int i = 0; i < n; i++) {
- final ParseTree subTree = eles.get(i);
- final Geometry sub = (Geometry) convertExpression(subTree, ff).evaluate(null);
- subs[i] = sub;
+ case GEOMETRYCOLLECTION: {
+ final ParseTree st = tree.getChild(1);
+ final Geometry geom;
+ if (isEmptyToken(st)) {
+ geom = GF.createGeometryCollection(new Geometry[0]);
+ } else {
+ final List<ExpressionGeometryContext> eles = exp.expressionGeometry();
+ final int n = eles.size();
+ final Geometry[] subs = new Geometry[n];
+ for (int i=0; i<n; i++) {
+ final ParseTree subTree = eles.get(i);
+ final Geometry sub = (Geometry) convertExpression(subTree, ff).apply(null);
+ subs[i] = sub;
+ }
+ geom = GF.createGeometryCollection(subs);
}
- geom = GF.createGeometryCollection(subs);
+ return ff.literal(geom);
}
- return ff.literal(geom);
- } else if (ENVELOPE == type) {
- final ParseTree st = (ParseTree) tree.getChild(1);
- final Geometry geom;
- if (isEmptyToken(st)) {
- geom = GF.createPolygon(GF.createLinearRing(new Coordinate[0]), new LinearRing[0]);
- } else {
- final List<ExpressionUnaryContext> unaries = exp.expressionUnary();
- final double west = unaryAsNumber(unaries.get(0)).doubleValue();
- final double east = unaryAsNumber(unaries.get(1)).doubleValue();
- final double north = unaryAsNumber(unaries.get(2)).doubleValue();
- final double south = unaryAsNumber(unaries.get(3)).doubleValue();
- final LinearRing contour = GF.createLinearRing(new Coordinate[]{
- new Coordinate(west, north),
- new Coordinate(east, north),
- new Coordinate(east, south),
- new Coordinate(west, south),
- new Coordinate(west, north)
- });
- geom = GF.createPolygon(contour, new LinearRing[0]);
+ case ENVELOPE: {
+ final ParseTree st = tree.getChild(1);
+ final Geometry geom;
+ if (isEmptyToken(st)) {
+ geom = GF.createPolygon(GF.createLinearRing(new Coordinate[0]), new LinearRing[0]);
+ } else {
+ final List<ExpressionUnaryContext> unaries = exp.expressionUnary();
+ final double west = unaryAsNumber(unaries.get(0)).doubleValue();
+ final double east = unaryAsNumber(unaries.get(1)).doubleValue();
+ final double north = unaryAsNumber(unaries.get(2)).doubleValue();
+ final double south = unaryAsNumber(unaries.get(3)).doubleValue();
+ final LinearRing contour = GF.createLinearRing(new Coordinate[]{
+ new Coordinate(west, north),
+ new Coordinate(east, north),
+ new Coordinate(east, south),
+ new Coordinate(west, south),
+ new Coordinate(west, north)
+ });
+ geom = GF.createPolygon(contour, new LinearRing[0]);
+ }
+ return ff.literal(geom);
}
- return ff.literal(geom);
}
return convertExpression(tree.getChild(0), ff);
}
@@ -383,9 +384,8 @@ public final class CQL {
return tree instanceof TerminalNode && ((TerminalNode) tree).getSymbol().getType() == EMPTY;
}
- private static final Number unaryAsNumber(ExpressionUnaryContext tree) {
+ private static Number unaryAsNumber(ExpressionUnaryContext exp) {
//: UNARY? expressionNum ;
- final ExpressionUnaryContext exp = (ExpressionUnaryContext) tree;
final boolean negate = (exp.UNARY() != null && exp.UNARY().getSymbol().getText().equals("-"));
final ExpressionNumContext num = exp.expressionNum();
if (num.INT() != null) {
@@ -411,10 +411,21 @@ public final class CQL {
return GF.getCoordinateSequenceFactory().create(coords);
}
+ private static Unit<Length> parseLengthUnit(final Expression<Feature,?> unitExp) {
+ Object value = unitExp.apply(null);
+ if (value != null) {
+ return Units.ensureLinear(Units.valueOf(value.toString()));
+ }
+ if (unitExp instanceof ValueReference<?,?>) {
+ value = ((ValueReference<?,?>) unitExp).getXPath();
+ }
+ throw new IllegalArgumentException("Unit `" + value + "` is not a literal.");
+ }
+
/**
* Convert the given tree in a Filter.
*/
- private static Filter convertFilter(ParseTree tree, FilterFactory2 ff) throws CQLException {
+ private static Filter<? super Feature> convertFilter(ParseTree tree, FilterFactory<Feature,Object,Object> ff) throws CQLException {
if (tree instanceof FilterContext) {
//: filter (AND filter)+
//| filter (OR filter)+
@@ -437,11 +448,11 @@ public final class CQL {
} else if (!exp.AND().isEmpty()) {
//: filter (AND filter)+
- final List<Filter> subs = new ArrayList<Filter>();
+ final List<Filter<? super Feature>> subs = new ArrayList<>();
for (FilterContext f : exp.filter()) {
- final Filter sub = convertFilter(f, ff);
- if (sub instanceof And) {
- subs.addAll(((And) sub).getChildren());
+ final Filter<? super Feature> sub = convertFilter(f, ff);
+ if (sub.getOperatorType() == LogicalOperatorName.AND) {
+ subs.addAll(((LogicalOperator<? super Feature>) sub).getOperands());
} else {
subs.add(sub);
}
@@ -449,11 +460,11 @@ public final class CQL {
return ff.and(subs);
} else if (!exp.OR().isEmpty()) {
//| filter (OR filter)+
- final List<Filter> subs = new ArrayList<Filter>();
+ final List<Filter<? super Feature>> subs = new ArrayList<>();
for (FilterContext f : exp.filter()) {
- final Filter sub = convertFilter(f, ff);
- if (sub instanceof Or) {
- subs.addAll(((Or) sub).getChildren());
+ final Filter<? super Feature> sub = convertFilter(f, ff);
+ if (sub.getOperatorType() == LogicalOperatorName.OR) {
+ subs.addAll(((LogicalOperator<? super Feature>) sub).getOperands());
} else {
subs.add(sub);
}
@@ -463,7 +474,6 @@ public final class CQL {
//| LPAREN filter RPAREN
return convertFilter(exp.filter(0), ff);
}
-
} else if (tree instanceof FilterTermContext) {
//: expression
// (
@@ -492,15 +502,13 @@ public final class CQL {
final FilterTermContext exp = (FilterTermContext) tree;
final List<ExpressionContext> exps = exp.expression();
-
if (exp.COMPARE() != null) {
// expression COMPARE expression
final String text = exp.COMPARE().getText();
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
-
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
if ("=".equals(text)) {
- return ff.equals(left, right);
+ return ff.equal(left, right);
} else if ("<>".equals(text)) {
return ff.notEqual(left, right);
} else if (">".equals(text)) {
@@ -511,33 +519,35 @@ public final class CQL {
return ff.greaterOrEqual(left, right);
} else if ("<=".equals(text)) {
return ff.lessOrEqual(left, right);
- } else if ("<=".equals(text)) {
- return ff.lessOrEqual(left, right);
}
} else if (exp.IN() != null) {
// expression NOT? IN LPAREN (expressionFctParam )? RPAREN
- final Expression val = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> val = convertExpression(exps.get(0), ff);
final ExpressionFctParamContext prm = exp.expressionFctParam();
final List<ExpressionContext> params = prm.expression();
- final List<Expression> subexps = new ArrayList<Expression>();
+ final List<Expression<Feature,?>> subexps = new ArrayList<>();
for (int i = 0, n = params.size(); i < n; i++) {
subexps.add(convertExpression(params.get(i), ff));
}
-
final int size = subexps.size();
- final Filter selection;
- if (size == 0) {
- selection = Filter.EXCLUDE;
- } else if (size == 1) {
- selection = ff.equals(val, subexps.get(0));
- } else {
- final List<Filter> filters = new ArrayList<Filter>();
- for (Expression e : subexps) {
- filters.add(ff.equals(val, e));
+ final Filter<Feature> selection;
+ switch (size) {
+ case 0: {
+ selection = Filter.exclude();
+ break;
+ }
+ case 1: {
+ selection = ff.equal(val, subexps.get(0));
+ break;
+ }
+ default: {
+ final List<Filter<? super Feature>> filters = new ArrayList<>();
+ for (Expression<Feature,?> e : subexps) {
+ filters.add(ff.equal(val, e));
+ } selection = ff.or(filters);
+ break;
}
- selection = ff.or(filters);
}
-
if (exp.NOT() != null) {
return ff.not(selection);
} else {
@@ -545,129 +555,110 @@ public final class CQL {
}
} else if (exp.BETWEEN() != null) {
// expression BETWEEN expression AND expression
- final Expression exp1 = convertExpression(exps.get(0), ff);
- final Expression exp2 = convertExpression(exps.get(1), ff);
- final Expression exp3 = convertExpression(exps.get(2), ff);
+ final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> exp3 = convertExpression(exps.get(2), ff);
return ff.between(exp1, exp2, exp3);
-
} else if (exp.LIKE() != null) {
// expression NOT? LIKE expression
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
if (exp.NOT() != null) {
- return ff.not(ff.like(left, right.evaluate(null, String.class), "%", "_", "\\", true));
+ return ff.not(ff.like(left, right.apply(null).toString(), '%', '_', '\\', true));
} else {
- return ff.like(left, right.evaluate(null, String.class), "%", "_", "\\", true);
+ return ff.like(left, right.apply(null).toString(), '%', '_', '\\', true);
}
-
} else if (exp.ILIKE() != null) {
// expression NOT? LIKE expression
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
if (exp.NOT() != null) {
- return ff.not(ff.like(left, right.evaluate(null, String.class), "%", "_", "\\", false));
+ return ff.not(ff.like(left, right.apply(null).toString(), '%', '_', '\\', false));
} else {
- return ff.like(left, right.evaluate(null, String.class), "%", "_", "\\", false);
+ return ff.like(left, right.apply(null).toString(), '%', '_', '\\', false);
}
-
} else if (exp.IS() != null) {
// expression IS NOT? NULL
- final Expression exp1 = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
if (exp.NOT() != null) {
return ff.not(ff.isNull(exp1));
} else {
return ff.isNull(exp1);
}
-
} else if (exp.AFTER() != null) {
// expression AFTER expression
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
return ff.after(left, right);
-
} else if (exp.ANYINTERACTS() != null) {
// expression ANYINTERACTS expression
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
return ff.anyInteracts(left, right);
-
} else if (exp.BEFORE() != null) {
// expression BEFORE expression
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
return ff.before(left, right);
-
} else if (exp.BEGINS() != null) {
// expression BEGINS expression
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
return ff.begins(left, right);
-
} else if (exp.BEGUNBY() != null) {
// expression BEGUNBY expression
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
return ff.begunBy(left, right);
-
} else if (exp.DURING() != null) {
// expression DURING expression
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
return ff.during(left, right);
-
} else if (exp.ENDEDBY() != null) {
// expression ENDEDBY expression
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
return ff.endedBy(left, right);
-
} else if (exp.ENDS() != null) {
// expression ENDS expression
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
return ff.ends(left, right);
-
} else if (exp.MEETS() != null) {
// expression MEETS expression
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
return ff.meets(left, right);
-
} else if (exp.METBY() != null) {
// expression METBY expression
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
return ff.metBy(left, right);
-
} else if (exp.OVERLAPPEDBY() != null) {
// expression OVERLAPPEDBY expression
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
return ff.overlappedBy(left, right);
-
} else if (exp.TCONTAINS() != null) {
// expression TCONTAINS expression
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
return ff.tcontains(left, right);
-
} else if (exp.TEQUALS() != null) {
// expression TEQUALS expression
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
return ff.tequals(left, right);
-
} else if (exp.TOVERLAPS() != null) {
// expression TOVERLAPS expression
- final Expression left = convertExpression(exps.get(0), ff);
- final Expression right = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> left = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> right = convertExpression(exps.get(1), ff);
return ff.toverlaps(left, right);
-
} else if (exp.filterGeometry() != null) {
// expression filterGeometry
return convertFilter(exp.filterGeometry(), ff);
}
-
} else if (tree instanceof FilterGeometryContext) {
//: BBOX LPAREN (PROPERTY_NAME|NAME) COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary COMMA expressionUnary (COMMA TEXT)? RPAREN
//| BEYOND LPAREN expression COMMA expression COMMA expression COMMA expression RPAREN
@@ -683,67 +674,67 @@ public final class CQL {
final FilterGeometryContext exp = (FilterGeometryContext) tree;
final List<ExpressionContext> exps = exp.expression();
-
if (exp.BBOX() != null) {
- final Expression prop = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> prop = convertExpression(exps.get(0), ff);
final double v1 = unaryAsNumber(exp.expressionUnary(0)).doubleValue();
final double v2 = unaryAsNumber(exp.expressionUnary(1)).doubleValue();
final double v3 = unaryAsNumber(exp.expressionUnary(2)).doubleValue();
final double v4 = unaryAsNumber(exp.expressionUnary(3)).doubleValue();
- String crs = null;
- if (exp.TEXT() != null) {
- crs = convertExpression(exp.TEXT(), ff).evaluate(null, String.class);
+ GeneralEnvelope env;
+ if (exp.TEXT() != null) try {
+ String crs = convertExpression(exp.TEXT(), ff).apply(null).toString();
+ env = new GeneralEnvelope(CRS.forCode(crs));
+ } catch (FactoryException e) {
+ throw new CQLException("Can not parse CRS code.", e);
+ } else {
+ env = new GeneralEnvelope(2);
}
- return ff.bbox(prop, v1, v2, v3, v4, crs);
+ env.setRange(0, v1, v3);
+ env.setRange(1, v2, v4);
+ return ff.bbox(prop, env);
} else if (exp.BEYOND() != null) {
- final Expression exp1 = convertExpression(exps.get(0), ff);
- final Expression exp2 = convertExpression(exps.get(1), ff);
- final double distance = convertExpression(exps.get(2), ff).evaluate(null, Double.class);
- final Expression unitExp = convertExpression(exps.get(3), ff);
- final String unit = (unitExp instanceof PropertyName)
- ? ((PropertyName) unitExp).getPropertyName()
- : unitExp.evaluate(null, String.class);
- return ff.beyond(exp1, exp2, distance, unit);
+ final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
+ final double distance = ((Number) convertExpression(exps.get(2), ff).apply(null)).doubleValue();
+ final Unit<Length> unit = parseLengthUnit(convertExpression(exps.get(3), ff));
+ return ff.beyond(exp1, exp2, Quantities.create(distance, unit));
} else if (exp.CONTAINS() != null) {
- final Expression exp1 = convertExpression(exps.get(0), ff);
- final Expression exp2 = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
return ff.contains(exp1, exp2);
} else if (exp.CROSSES() != null) {
- final Expression exp1 = convertExpression(exps.get(0), ff);
- final Expression exp2 = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
return ff.crosses(exp1, exp2);
} else if (exp.DISJOINT() != null) {
- final Expression exp1 = convertExpression(exps.get(0), ff);
- final Expression exp2 = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
return ff.disjoint(exp1, exp2);
} else if (exp.DWITHIN() != null) {
- final Expression exp1 = convertExpression(exps.get(0), ff);
- final Expression exp2 = convertExpression(exps.get(1), ff);
- final double distance = convertExpression(exps.get(2), ff).evaluate(null, Double.class);
- final Expression unitExp = convertExpression(exps.get(3), ff);
- final String unit = (unitExp instanceof PropertyName)
- ? ((PropertyName) unitExp).getPropertyName()
- : unitExp.evaluate(null, String.class);
- return ff.dwithin(exp1, exp2, distance, unit);
+ final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
+ final double distance = ((Number) convertExpression(exps.get(2), ff).apply(null)).doubleValue();
+ final Unit<Length> unit = parseLengthUnit(convertExpression(exps.get(3), ff));
+ return ff.within(exp1, exp2, Quantities.create(distance, unit));
} else if (exp.EQUALS() != null) {
- final Expression exp1 = convertExpression(exps.get(0), ff);
- final Expression exp2 = convertExpression(exps.get(1), ff);
- return ff.equal(exp1, exp2);
+ final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
+ return ff.equals(exp1, exp2);
} else if (exp.INTERSECTS() != null) {
- final Expression exp1 = convertExpression(exps.get(0), ff);
- final Expression exp2 = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
return ff.intersects(exp1, exp2);
} else if (exp.OVERLAPS() != null) {
- final Expression exp1 = convertExpression(exps.get(0), ff);
- final Expression exp2 = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
return ff.overlaps(exp1, exp2);
} else if (exp.TOUCHES() != null) {
- final Expression exp1 = convertExpression(exps.get(0), ff);
- final Expression exp2 = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
return ff.touches(exp1, exp2);
} else if (exp.WITHIN() != null) {
- final Expression exp1 = convertExpression(exps.get(0), ff);
- final Expression exp2 = convertExpression(exps.get(1), ff);
+ final Expression<Feature,?> exp1 = convertExpression(exps.get(0), ff);
+ final Expression<Feature,?> exp2 = convertExpression(exps.get(1), ff);
return ff.within(exp1, exp2);
}
}
diff --git a/core/sis-cql/src/main/java/org/apache/sis/cql/FilterToCQLVisitor.java b/core/sis-cql/src/main/java/org/apache/sis/cql/FilterToCQLVisitor.java
index 19ba6d4..76c7c67 100644
--- a/core/sis-cql/src/main/java/org/apache/sis/cql/FilterToCQLVisitor.java
+++ b/core/sis-cql/src/main/java/org/apache/sis/cql/FilterToCQLVisitor.java
@@ -16,669 +16,311 @@
*/
package org.apache.sis.cql;
-import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.List;
+import java.util.Locale;
import java.util.regex.Pattern;
+import java.time.temporal.TemporalAccessor;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import javax.measure.Unit;
+import javax.measure.Quantity;
+import org.opengis.util.CodeList;
+import org.apache.sis.measure.UnitFormat;
+import org.apache.sis.geometry.GeneralEnvelope;
+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.util.StandardDateFormat;
-import org.locationtech.jts.geom.Envelope;
+// Branch-dependent imports
+import org.opengis.feature.Feature;
+import org.opengis.filter.Filter;
+import org.opengis.filter.Literal;
+import org.opengis.filter.ValueReference;
+import org.opengis.filter.LogicalOperator;
+import org.opengis.filter.LogicalOperatorName;
+import org.opengis.filter.SpatialOperatorName;
+import org.opengis.filter.DistanceOperatorName;
+import org.opengis.filter.TemporalOperatorName;
+import org.opengis.filter.BinarySpatialOperator;
+import org.opengis.filter.ComparisonOperatorName;
+import org.opengis.filter.BetweenComparisonOperator;
+import org.opengis.filter.LikeOperator;
+import org.opengis.filter.Expression;
+
+// Optional dependencies
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.WKTWriter;
-import org.opengis.filter.And;
-import org.opengis.filter.ExcludeFilter;
-import org.opengis.filter.Filter;
-import org.opengis.filter.FilterVisitor;
-import org.opengis.filter.Id;
-import org.opengis.filter.IncludeFilter;
-import org.opengis.filter.Not;
-import org.opengis.filter.Or;
-import org.opengis.filter.PropertyIsBetween;
-import org.opengis.filter.PropertyIsEqualTo;
-import org.opengis.filter.PropertyIsGreaterThan;
-import org.opengis.filter.PropertyIsGreaterThanOrEqualTo;
-import org.opengis.filter.PropertyIsLessThan;
-import org.opengis.filter.PropertyIsLessThanOrEqualTo;
-import org.opengis.filter.PropertyIsLike;
-import org.opengis.filter.PropertyIsNil;
-import org.opengis.filter.PropertyIsNotEqualTo;
-import org.opengis.filter.PropertyIsNull;
-import org.opengis.filter.expression.Add;
-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.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.After;
-import org.opengis.filter.temporal.AnyInteracts;
-import org.opengis.filter.temporal.Before;
-import org.opengis.filter.temporal.Begins;
-import org.opengis.filter.temporal.BegunBy;
-import org.opengis.filter.temporal.During;
-import org.opengis.filter.temporal.EndedBy;
-import org.opengis.filter.temporal.Ends;
-import org.opengis.filter.temporal.Meets;
-import org.opengis.filter.temporal.MetBy;
-import org.opengis.filter.temporal.OverlappedBy;
-import org.opengis.filter.temporal.TContains;
-import org.opengis.filter.temporal.TEquals;
-import org.opengis.filter.temporal.TOverlaps;
/**
* Visitor to convert a Filter in CQL.
- * Returned object is a StringBuilder containing the CQL text.
*
* @author Johann Sorel (Geomatys)
- * @version 1.0
- * @since 1.0
+ * @version 1.1
+ * @since 1.1
* @module
*/
-final class FilterToCQLVisitor implements FilterVisitor, ExpressionVisitor {
+final class FilterToCQLVisitor extends Visitor<Feature,StringBuilder> {
static final FilterToCQLVisitor INSTANCE = new FilterToCQLVisitor();
/**
* Pattern to check for property name to escape against regExp
*/
- private final Pattern patternPropertyName = Pattern.compile("[,+\\-/*\\t\\n\\r\\d\\s]");
-
- private FilterToCQLVisitor() {
- }
-
- private static StringBuilder toStringBuilder(final Object o) {
- if (o instanceof StringBuilder) {
- return (StringBuilder) o;
- }
- return new StringBuilder();
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // FILTER //////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////
- @Override
- public Object visitNullFilter(final Object o) {
- throw new UnsupportedOperationException("Null filter not supported in CQL.");
- }
-
- @Override
- public Object visit(final ExcludeFilter filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- sb.append("1=0");
- return sb;
- }
+ private final Pattern patternPropertyName;
- @Override
- public Object visit(final IncludeFilter filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- sb.append("1=1");
- return sb;
- }
+ /**
+ * Formatter to use for unit symbol. Not thread-safe; usage must be synchronized.
+ */
+ private final UnitFormat unitFormat;
- @Override
- public Object visit(final And filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- final List<Filter> filters = filter.getChildren();
- if (filters != null && !filters.isEmpty()) {
- final int size = filters.size();
- sb.append('(');
- for (int i = 0, n = size - 1; i < n; i++) {
- filters.get(i).accept(this, sb);
- sb.append(" AND ");
+ /**
+ * Creates a new visitor.
+ */
+ private FilterToCQLVisitor() {
+ patternPropertyName = Pattern.compile("[,+\\-/*\\t\\n\\r\\d\\s]");
+ unitFormat = new UnitFormat(Locale.US);
+ unitFormat.setStyle(UnitFormat.Style.NAME);
+
+ constant(Filter.exclude(), "1=0");
+ constant(Filter.include(), "1=1");
+ operatorBetweenValues(LogicalOperatorName.AND, "AND");
+ operatorBetweenValues(LogicalOperatorName.OR, "OR");
+ setFilterHandler(LogicalOperatorName.NOT, (f,sb) -> {
+ final LogicalOperator<Feature> filter = (LogicalOperator<Feature>) f;
+ format(sb.append("NOT "), filter.getOperands().get(0));
+ });
+ setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_BETWEEN), (f,sb) -> {
+ final BetweenComparisonOperator<Feature> filter = (BetweenComparisonOperator<Feature>) f;
+ format(sb, filter.getExpression());
+ format(sb.append(" BETWEEN "), filter.getLowerBoundary());
+ format(sb.append(" AND "), filter.getUpperBoundary());
+ });
+ operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_EQUAL_TO, "=");
+ operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_NOT_EQUAL_TO, "<>");
+ operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN, ">");
+ operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO, ">=");
+ operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_LESS_THAN, "<");
+ operatorBetweenValues(ComparisonOperatorName.PROPERTY_IS_LESS_THAN_OR_EQUAL_TO, "<=");
+ setFilterHandler(ComparisonOperatorName.valueOf(FunctionNames.PROPERTY_IS_LIKE), (f,sb) -> {
+ final LikeOperator<Feature> filter = (LikeOperator<Feature>) f;
+ List<Expression<? super Feature, ?>> operands = f.getExpressions();
+ format(sb, operands.get(0));
+ // TODO: ILIKE is not standard SQL.
+ sb.append(filter.isMatchingCase() ? " LIKE " : " ILIKE ");
+ // TODO: convert wildcards and escape to SQL 92.
+ format(sb, operands.get(1));
+ });
+ operatorAfterValue(FunctionNames.PROPERTY_IS_NULL, " IS NULL");
+ operatorAfterValue(FunctionNames.PROPERTY_IS_NIL, " IS NIL");
+ /*
+ * Spatial filters.
+ */
+ setFilterHandler(SpatialOperatorName.BBOX, (f,sb) -> {
+ final BinarySpatialOperator<Feature> filter = (BinarySpatialOperator<Feature>) f;
+ final Expression<? super Feature, ?> left = filter.getOperand1();
+ final Expression<? super Feature, ?> right = filter.getOperand2();
+ final ValueReference<? super Feature, ?> pName =
+ (left instanceof ValueReference) ? (ValueReference<? super Feature, ?>) left :
+ (right instanceof ValueReference) ? (ValueReference<? super Feature, ?>) right : null;
+ final Object lit = ((left instanceof Literal)
+ ? (Literal<? super Feature, ?>) left
+ : (Literal<? super Feature, ?>) right).getValue(); // TODO: potential classCastException.
+
+ final GeneralEnvelope e = Geometries.wrap(lit).map(GeometryWrapper::getEnvelope).orElse(null);
+ if (e != null) {
+ if (e.getDimension() > 2) {
+ throw new UnsupportedOperationException("Only 2D envelopes accepted");
+ }
+ sb.append("BBOX(").append(pName.getXPath()).append(", ");
+ sb.append(e.getMinimum(0)).append(", ")
+ .append(e.getMaximum(0)).append(", ")
+ .append(e.getMinimum(1)).append(", ")
+ .append(e.getMaximum(1)).append(')');
+ } else {
+ // Use writing BBOX(exp1,exp2).
+ format(sb.append("BBOX("), left);
+ format(sb.append(','), right);
+ sb.append(')');
}
- filters.get(size - 1).accept(this, sb);
- sb.append(')');
- }
- return sb;
- }
-
- @Override
- public Object visit(final Or filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- final List<Filter> filters = filter.getChildren();
- if (filters != null && !filters.isEmpty()) {
- final int size = filters.size();
- sb.append('(');
- for (int i = 0, n = size - 1; i < n; i++) {
- filters.get(i).accept(this, sb);
- sb.append(" OR ");
+ });
+ for (final SpatialOperatorName type : SpatialOperatorName.values()) {
+ if (type != SpatialOperatorName.BBOX) {
+ function(type, type.identifier().toUpperCase(Locale.US));
+ if (type == SpatialOperatorName.OVERLAPS) break;
}
- filters.get(size - 1).accept(this, sb);
- sb.append(')');
}
- return sb;
- }
-
- @Override
- public Object visit(final Id filter, final Object o) {
- throw new UnsupportedOperationException("ID filter not supported in CQL.");
- }
-
- @Override
- public Object visit(final Not filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- sb.append("NOT ");
- filter.getFilter().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(final PropertyIsBetween filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression().accept(this, sb);
- sb.append(" BETWEEN ");
- filter.getLowerBoundary().accept(this, sb);
- sb.append(" AND ");
- filter.getUpperBoundary().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(final PropertyIsEqualTo filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" = ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(final PropertyIsNotEqualTo filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" <> ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(final PropertyIsGreaterThan filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" > ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(final PropertyIsGreaterThanOrEqualTo filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" >= ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(final PropertyIsLessThan filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" < ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(final PropertyIsLessThanOrEqualTo filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" <= ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(final PropertyIsLike filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- final char escape = filter.getEscape().charAt(0);
- final char wildCard = filter.getWildCard().charAt(0);
- final char singleChar = filter.getSingleChar().charAt(0);
- final boolean matchingCase = filter.isMatchingCase();
- final String literal = filter.getLiteral();
- //TODO wild card and escape encoded to sql 92
- final String pattern = literal;
-
- filter.getExpression().accept(this, sb);
-
- if (matchingCase) {
- sb.append(" LIKE ");
- } else {
- sb.append(" ILIKE ");
+ function(DistanceOperatorName.WITHIN, "DWITHIN");
+ function(DistanceOperatorName.BEYOND, "BEYOND");
+ for (final TemporalOperatorName type : TemporalOperatorName.values()) {
+ function(type, type.identifier().toUpperCase(Locale.US));
+ if (type == TemporalOperatorName.ANY_INTERACTS) break;
}
- sb.append('\'');
- sb.append(pattern);
- sb.append('\'');
- return sb;
- }
-
- @Override
- public Object visit(final PropertyIsNull filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression().accept(this, sb);
- sb.append(" IS NULL");
- return sb;
- }
-
- @Override
- public Object visit(PropertyIsNil filter, Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression().accept(this, sb);
- sb.append(" IS NIL");
- return sb;
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // GEOMETRY FILTER /////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////
- @Override
- public Object visit(final BBOX filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
-
- final Expression left = filter.getExpression1();
- final Expression right = filter.getExpression2();
-
- final PropertyName pName = left instanceof PropertyName ? (PropertyName) left :
- right instanceof PropertyName ? (PropertyName) right : null;
- final Literal lit = left instanceof Literal ? (Literal) left :
- right instanceof Literal ? (Literal) right : null;
- final Envelope jtsEnv = lit.getValue() instanceof Envelope ? (Envelope) lit.getValue() : null;
- org.opengis.geometry.Envelope gisEnv = lit.getValue() instanceof org.opengis.geometry.Envelope ?
- (org.opengis.geometry.Envelope) lit.getValue() : null;
- if (pName != null && gisEnv != null || jtsEnv != null) {
- //use writing : BBOX(att,v1,v2,v3,v4)
- sb.append("BBOX(");
- sb.append(pName.getPropertyName());
- sb.append(',');
- final double minX, minY, maxX, maxY;
- if (gisEnv != null) {
- if (gisEnv.getDimension() > 2) throw new UnsupportedOperationException("Only 2D envelopes accepted");
- minX = gisEnv.getMinimum(0);
- maxX = gisEnv.getMaximum(0);
- minY = gisEnv.getMinimum(1);
- maxY = gisEnv.getMaximum(1);
+ /*
+ * Expressions
+ */
+ setExpressionHandler(FunctionNames.Literal, (e,sb) -> {
+ final Literal<Feature,?> exp = (Literal<Feature,?>) e;
+ final Object value = exp.getValue();
+ if (value instanceof Quantity<?>) {
+ final Quantity<?> q = (Quantity<?>) value;
+ final Unit<?> unit = q.getUnit();
+ sb.append(q.getValue().doubleValue()).append(", '");
+ try {
+ synchronized (unitFormat) {
+ unitFormat.format(unit, sb);
+ }
+ } catch (IOException ex) {
+ throw new UncheckedIOException(ex); // Should never happen.
+ }
+ sb.append('\'');
+ } else if (value instanceof Number) {
+ sb.append(value);
+ } else if (value instanceof Date) {
+ final Date date = (Date) value;
+ sb.append(StandardDateFormat.FORMAT.format(date.toInstant()));
+ } else if (value instanceof TemporalAccessor) {
+ final TemporalAccessor date = (TemporalAccessor) value;
+ sb.append(StandardDateFormat.FORMAT.format(date));
+ } else if (value instanceof Geometry) {
+ final Geometry geometry = (Geometry) value;
+ final WKTWriter writer = new WKTWriter();
+ sb.append(writer.write(geometry));
} else {
- minX = jtsEnv.getMinX();
- maxX = jtsEnv.getMaxX();
- minY = jtsEnv.getMinY();
- maxY = jtsEnv.getMaxY();
+ sb.append('\'').append(value).append('\'');
}
- sb.append(minX);
- sb.append(',');
- sb.append(maxX);
- sb.append(',');
- sb.append(minY);
- sb.append(',');
- sb.append(maxY);
- sb.append(')');
-
- } else {
- //use writing BBOX(exp1,exp2)
- sb.append("BBOX(");
- left.accept(this, sb);
- sb.append(',');
- right.accept(this, sb);
- sb.append(')');
- }
-
- return sb;
- }
-
- @Override
- public Object visit(final Beyond filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- sb.append("BEYOND(");
- filter.getExpression1().accept(this, sb);
- sb.append(',');
- filter.getExpression2().accept(this, sb);
- sb.append(')');
- return sb;
- }
-
- @Override
- public Object visit(final Contains filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- sb.append("CONTAINS(");
- filter.getExpression1().accept(this, sb);
- sb.append(',');
- filter.getExpression2().accept(this, sb);
- sb.append(')');
- return sb;
- }
-
- @Override
- public Object visit(final Crosses filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- sb.append("CROSSES(");
- filter.getExpression1().accept(this, sb);
- sb.append(',');
- filter.getExpression2().accept(this, sb);
- sb.append(')');
- return sb;
- }
-
- @Override
- public Object visit(final Disjoint filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- sb.append("DISJOINT(");
- filter.getExpression1().accept(this, sb);
- sb.append(',');
- filter.getExpression2().accept(this, sb);
- sb.append(')');
- return sb;
- }
-
- @Override
- public Object visit(final DWithin filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- sb.append("DWITHIN(");
- filter.getExpression1().accept(this, sb);
- sb.append(',');
- filter.getExpression2().accept(this, sb);
- sb.append(')');
- return sb;
- }
-
- @Override
- public Object visit(final Equals filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- sb.append("EQUALS(");
- filter.getExpression1().accept(this, sb);
- sb.append(',');
- filter.getExpression2().accept(this, sb);
- sb.append(')');
- return sb;
- }
-
- @Override
- public Object visit(final Intersects filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- sb.append("INTERSECTS(");
- filter.getExpression1().accept(this, sb);
- sb.append(',');
- filter.getExpression2().accept(this, sb);
- sb.append(')');
- return sb;
- }
-
- @Override
- public Object visit(final Overlaps filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- sb.append("OVERLAPS(");
- filter.getExpression1().accept(this, sb);
- sb.append(',');
- filter.getExpression2().accept(this, sb);
- sb.append(')');
- return sb;
- }
-
- @Override
- public Object visit(final Touches filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- sb.append("TOUCHES(");
- filter.getExpression1().accept(this, sb);
- sb.append(',');
- filter.getExpression2().accept(this, sb);
- sb.append(')');
- return sb;
- }
-
- @Override
- public Object visit(final Within filter, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- sb.append("WITHIN(");
- filter.getExpression1().accept(this, sb);
- sb.append(',');
- filter.getExpression2().accept(this, sb);
- sb.append(')');
- return sb;
- }
-
- ////////////////////////////////////////////////////////////////////////////
- // TEMPORAL FILTER /////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////
-
- @Override
- public Object visit(After filter, Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" AFTER ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(AnyInteracts filter, Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" ANYINTERACTS ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(Before filter, Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" BEFORE ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(Begins filter, Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" BEGINS ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(BegunBy filter, Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" BEGUNBY ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(During filter, Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" DURING ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(EndedBy filter, Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" ENDEDBY ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(Ends filter, Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" ENDS ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(Meets filter, Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" MEETS ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(MetBy filter, Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" METBY ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(OverlappedBy filter, Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" OVERLAPPEDBY ");
- filter.getExpression2().accept(this, sb);
- return sb;
- }
-
- @Override
- public Object visit(TContains filter, Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" TCONTAINS ");
- filter.getExpression2().accept(this, sb);
- return sb;
+ });
+ setExpressionHandler(FunctionNames.ValueReference, (e,sb) -> {
+ final ValueReference<Feature,?> exp = (ValueReference<Feature,?>) e;
+ final String name = exp.getXPath();
+ if (patternPropertyName.matcher(name).find()) {
+ // Escape for special chars
+ sb.append('"').append(name).append('"');
+ } else {
+ sb.append(name);
+ }
+ });
+ arithmetic(FunctionNames.Add, '+');
+ arithmetic(FunctionNames.Divide, '/');
+ arithmetic(FunctionNames.Multiply, '*');
+ arithmetic(FunctionNames.Subtract, '-');
}
- @Override
- public Object visit(TEquals filter, Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" TEQUALS ");
- filter.getExpression2().accept(this, sb);
- return sb;
+ private void constant(final Filter<?> type, final String text) {
+ setFilterHandler(type.getOperatorType(), (f,sb) -> sb.append(text));
}
- @Override
- public Object visit(TOverlaps filter, Object o) {
- final StringBuilder sb = toStringBuilder(o);
- filter.getExpression1().accept(this, sb);
- sb.append(" TOVERLAPS ");
- filter.getExpression2().accept(this, sb);
- return sb;
+ private void operatorAfterValue(final String type, final String operator) {
+ setFilterHandler(ComparisonOperatorName.valueOf(type), (f,sb) -> {
+ format(sb, f.getExpressions().get(0));
+ sb.append(operator);
+ });
}
- ////////////////////////////////////////////////////////////////////////////
- // EXPRESSIONS /////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////
- @Override
- public Object visit(final Literal exp, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
-
- final Object value = exp.getValue();
- if (value instanceof Number) {
- final Number num = (Number) value;
- sb.append(num.toString());
- } else if (value instanceof Date) {
- final Date date = (Date) value;
- sb.append(StandardDateFormat.FORMAT.format(date.toInstant()));
- } else if (value instanceof TemporalAccessor) {
- final TemporalAccessor date = (TemporalAccessor) value;
- sb.append(StandardDateFormat.FORMAT.format(date));
- } else if (value instanceof Geometry) {
- final Geometry geometry = (Geometry) value;
- final WKTWriter writer = new WKTWriter();
- final String wkt = writer.write(geometry);
- sb.append(wkt);
- } else {
- sb.append('\'').append(value != null ? value.toString() : null).append('\'');
- }
- return sb;
+ private void operatorBetweenValues(final ComparisonOperatorName type, final String operator) {
+ setFilterHandler(type, (f,sb) -> {
+ final List<Expression<? super Feature, ?>> operands = f.getExpressions();
+ format(sb, operands.get(0));
+ final int n = operands.size();
+ for (int i=1; i<n; i++) {
+ // Should execute only once. If n>2, make the problem visible in the CQL.
+ format(sb.append(' ').append(operator).append(' '), operands.get(i));
+ }
+ });
}
- @Override
- public Object visit(final PropertyName exp, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- final String name = exp.getPropertyName();
- if (patternPropertyName.matcher(name).find()) {
- //escape for special chars
- sb.append('"').append(name).append('"');
- } else {
- sb.append(name);
- }
- return sb;
+ private void operatorBetweenValues(final LogicalOperatorName type, final String operator) {
+ setFilterHandler(type, (f,sb) -> {
+ final LogicalOperator<Feature> filter = (LogicalOperator<Feature>) f;
+ final List<Filter<? super Feature>> operands = filter.getOperands();
+ format(sb.append('('), operands.get(0));
+ final int n = operands.size();
+ for (int i=1; i<n; i++) {
+ format(sb.append(' ').append(operator).append(' '), operands.get(i));
+ }
+ sb.append(')');
+ });
}
- @Override
- public Object visit(final Add exp, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- exp.getExpression1().accept(this, sb);
- sb.append(" + ");
- exp.getExpression2().accept(this, sb);
- return sb;
+ private void function(final CodeList<?> type, final String operator) {
+ setFilterHandler(type, (f,sb) -> {
+ final List<Expression<? super Feature, ?>> operands = f.getExpressions();
+ sb.append(operator).append('(');
+ final int n = operands.size();
+ for (int i=0; i<n; i++) {
+ if (i != 0) sb.append(", ");
+ format(sb, operands.get(i));
+ }
+ sb.append(')');
+ });
}
- @Override
- public Object visit(final Divide exp, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- exp.getExpression1().accept(this, sb);
- sb.append(" / ");
- exp.getExpression2().accept(this, sb);
- return sb;
+ private void arithmetic(final String type, final char operator) {
+ setExpressionHandler(type, (e,sb) -> {
+ final List<Expression<? super Feature, ?>> parameters = e.getParameters();
+ format(sb, parameters.get(0));
+ final int n = parameters.size();
+ for (int i=1; i<n; i++) {
+ format(sb.append(' ').append(operator).append(' '), parameters.get(i));
+ }
+ });
}
- @Override
- public Object visit(final Multiply exp, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- exp.getExpression1().accept(this, sb);
- sb.append(" * ");
- exp.getExpression2().accept(this, sb);
- return sb;
+ /**
+ * Executes the registered action for the given filter.
+ *
+ * <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.
+ */
+ @SuppressWarnings("unchecked")
+ private void format(final StringBuilder sb, final Filter<? super Feature> filter) {
+ visit((Filter<Feature>) filter, sb);
}
- @Override
- public Object visit(final Subtract exp, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- exp.getExpression1().accept(this, sb);
- sb.append(" - ");
- exp.getExpression2().accept(this, sb);
- return sb;
+ /**
+ * Executes the registered action for the given expression.
+ * Throws an exception if the expression 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 Expression#getParameters()}. But the {@link BiConsumer} expects 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 exp}
+ * 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 exp the expression for which to execute an action based on its type.
+ * @throws UnsupportedOperationException if there is no action registered for the given expression.
+ */
+ @SuppressWarnings("unchecked")
+ private void format(final StringBuilder sb, final Expression<? super Feature, ?> expression) {
+ visit((Expression<Feature,?>) expression, sb);
}
@Override
- public Object visit(final Function exp, final Object o) {
- final StringBuilder sb = toStringBuilder(o);
- sb.append(exp.getName());
- sb.append('(');
- final List<Expression> exps = exp.getParameters();
- if (exps != null) {
- final int size = exps.size();
- if (size == 1) {
- exps.get(0).accept(this, sb);
- } else if (size > 1) {
- for (int i = 0, n = size - 1; i < n; i++) {
- exps.get(i).accept(this, sb);
- sb.append(" , ");
- }
- exps.get(size - 1).accept(this, sb);
- }
+ protected void typeNotFound(final String type, final Expression<Feature,?> e, final StringBuilder sb) {
+ final List<Expression<? super Feature,?>> exps = e.getParameters();
+ sb.append(type).append('(');
+ final int n = exps.size();
+ for (int i=0; i<n; i++) {
+ if (i != 0) sb.append(", ");
+ format(sb, exps.get(i));
}
sb.append(')');
- return sb;
- }
-
- @Override
- public Object visit(final NilExpression exp, final Object o) {
- throw new UnsupportedOperationException("NilExpression not supported in CQL.");
}
}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestCase.java b/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestCase.java
index badb0b4..eb76c58 100644
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestCase.java
+++ b/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestCase.java
@@ -16,27 +16,26 @@
*/
package org.apache.sis.cql;
-import java.time.Instant;
-import org.opengis.filter.FilterFactory2;
+import org.opengis.filter.FilterFactory;
import org.locationtech.jts.geom.GeometryFactory;
import org.apache.sis.filter.DefaultFilterFactory;
-import org.apache.sis.internal.util.StandardDateFormat;
import org.apache.sis.test.TestCase;
+import org.opengis.feature.Feature;
/**
* Base class of all CQL tests.
*
* @author Johann Sorel (Geomatys)
- * @version 1.0
- * @since 1.0
+ * @version 1.1
+ * @since 1.1
* @module
*/
-strictfp class CQLTestCase extends TestCase {
+abstract strictfp class CQLTestCase extends TestCase {
/**
* The factory to use for creating filter and expressions.
*/
- final FilterFactory2 FF;
+ final FilterFactory<Feature,Object,Object> FF;
/**
* The factory to use for creating Java Topology Suite (JTS) objects.
@@ -47,14 +46,7 @@ strictfp class CQLTestCase extends TestCase {
* Creates a new test case.
*/
CQLTestCase() {
- FF = new DefaultFilterFactory();
+ FF = DefaultFilterFactory.forFeatures();
GF = new GeometryFactory();
}
-
- /**
- * Returns a date from the given string.
- */
- static Instant parseDate(final String date) {
- return Instant.from(StandardDateFormat.FORMAT.parse(date));
- }
}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestSuite.java b/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestSuite.java
index aa8762b..b84d1e4 100644
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestSuite.java
+++ b/core/sis-cql/src/test/java/org/apache/sis/cql/CQLTestSuite.java
@@ -25,8 +25,8 @@ import org.junit.runners.Suite;
* All tests from the {@code sis-cql} module.
*
* @author Johann Sorel (Geomatys)
- * @version 1.0
- * @since 1.0
+ * @version 1.1
+ * @since 1.1
* @module
*/
@Suite.SuiteClasses({
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionReadingTest.java b/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionReadingTest.java
index 795b34e..6d3928f 100644
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionReadingTest.java
+++ b/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionReadingTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.cql;
+import java.time.Instant;
import java.text.ParseException;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
@@ -27,13 +28,10 @@ import org.locationtech.jts.geom.MultiPoint;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
-import org.opengis.filter.expression.Add;
-import org.opengis.filter.expression.Divide;
-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.feature.Feature;
+import org.opengis.filter.Expression;
+import org.opengis.filter.Literal;
+import org.opengis.filter.ValueReference;
import org.junit.Ignore;
import org.junit.Test;
@@ -44,36 +42,36 @@ import static org.junit.Assert.*;
* Test reading CQL expressions.
*
* @author Johann Sorel (Geomatys)
- * @version 1.0
- * @since 1.0
+ * @version 1.1
+ * @since 1.1
* @module
*/
public final strictfp class ExpressionReadingTest extends CQLTestCase {
@Test
- public void testPropertyName1() throws CQLException {
+ public void testValueReference1() throws CQLException {
final String cql = "geom";
final Object obj = CQL.parseExpression(cql);
- assertTrue(obj instanceof PropertyName);
- final PropertyName expression = (PropertyName) obj;
- assertEquals("geom", expression.getPropertyName());
+ assertTrue(obj instanceof ValueReference);
+ final ValueReference expression = (ValueReference) obj;
+ assertEquals("geom", expression.getXPath());
}
@Test
- public void testPropertyName2() throws CQLException {
+ public void testValueReference2() throws CQLException {
final String cql = "\"geom\"";
final Object obj = CQL.parseExpression(cql);
- assertTrue(obj instanceof PropertyName);
- final PropertyName expression = (PropertyName) obj;
- assertEquals("geom", expression.getPropertyName());
+ assertTrue(obj instanceof ValueReference);
+ final ValueReference expression = (ValueReference) obj;
+ assertEquals("geom", expression.getXPath());
}
@Test
- public void testPropertyName3() throws CQLException {
+ public void testValueReference3() throws CQLException {
final String cql = "ùth{e_$uglY^_pr@perté";
final Object obj = CQL.parseExpression(cql);
- assertTrue(obj instanceof PropertyName);
- final PropertyName expression = (PropertyName) obj;
- assertEquals("ùth{e_$uglY^_pr@perté", expression.getPropertyName());
+ assertTrue(obj instanceof ValueReference);
+ final ValueReference expression = (ValueReference) obj;
+ assertEquals("ùth{e_$uglY^_pr@perté", expression.getXPath());
}
@Test
@@ -148,38 +146,36 @@ public final strictfp class ExpressionReadingTest extends CQLTestCase {
assertEquals("Valle d'Aosta/Vallée d'Aoste", expression.getValue());
}
- @Ignore
@Test
+ @Ignore("Incomplete use if java.time in CQL parser.")
public void testDate() throws CQLException, ParseException{
//dates are expected to be formated in ISO 8601 : yyyy-MM-dd'T'HH:mm:ss'Z'
final String cql = "2012-03-21T05:42:36Z";
final Object obj = CQL.parseExpression(cql);
assertTrue(obj instanceof Literal);
final Literal expression = (Literal) obj;
- assertEquals(parseDate("2012-03-21T05:42:36Z"), expression.getValue());
+ assertEquals(Instant.parse("2012-03-21T05:42:36Z"), expression.getValue());
}
- @Ignore
@Test
+ @Ignore("Unreconized expression.")
public void testDuration() throws CQLException, ParseException{
final String cql = "P7Y6M5D4H3M2S";
final Object obj = CQL.parseExpression(cql);
assertTrue(obj instanceof Literal);
final Literal expression = (Literal) obj;
final long duration = (Long) expression.getValue();
-
assertEquals(236966582000l, duration);
}
- @Ignore
@Test
+ @Ignore("Unreconized expression.")
public void testDuration2() throws CQLException, ParseException{
final String cql = "T4H3M2S";
final Object obj = CQL.parseExpression(cql);
assertTrue(obj instanceof Literal);
final Literal expression = (Literal) obj;
final long duration = (Long) expression.getValue();
-
assertEquals(14582000,duration);
}
@@ -187,20 +183,19 @@ public final strictfp class ExpressionReadingTest extends CQLTestCase {
public void testAddition() throws CQLException {
final String cql = "3 + 2";
final Object obj = CQL.parseExpression(cql);
- assertTrue(obj instanceof Add);
- final Add expression = (Add) obj;
+ assertTrue(obj instanceof Expression);
+ final Expression expression = (Expression) obj;
assertEquals(FF.add(FF.literal(3), FF.literal(2)), expression);
}
- @Ignore
@Test
+ @Ignore("String cannot be cast to Number.")
public void testAddition2() throws CQLException {
final String cql = "'test' + '23'";
final Object obj = CQL.parseExpression(cql);
- assertTrue(obj instanceof Add);
- final Add expression = (Add) obj;
-
- Object res = expression.evaluate(null);
+ assertTrue(obj instanceof Expression);
+ final Expression expression = (Expression) obj;
+ Object res = expression.apply(null);
assertEquals("test23", res);
}
@@ -208,8 +203,8 @@ public final strictfp class ExpressionReadingTest extends CQLTestCase {
public void testSubstract() throws CQLException {
final String cql = "3 - 2";
final Object obj = CQL.parseExpression(cql);
- assertTrue(obj instanceof Subtract);
- final Subtract expression = (Subtract) obj;
+ assertTrue(obj instanceof Expression);
+ final Expression expression = (Expression) obj;
assertEquals(FF.subtract(FF.literal(3), FF.literal(2)), expression);
}
@@ -217,8 +212,8 @@ public final strictfp class ExpressionReadingTest extends CQLTestCase {
public void testMultiply() throws CQLException {
final String cql = "3 * 2";
final Object obj = CQL.parseExpression(cql);
- assertTrue(obj instanceof Multiply);
- final Multiply expression = (Multiply) obj;
+ assertTrue(obj instanceof Expression);
+ final Expression expression = (Expression) obj;
assertEquals(FF.multiply(FF.literal(3), FF.literal(2)), expression);
}
@@ -226,28 +221,28 @@ public final strictfp class ExpressionReadingTest extends CQLTestCase {
public void testDivide() throws CQLException {
final String cql = "3 / 2";
final Object obj = CQL.parseExpression(cql);
- assertTrue(obj instanceof Divide);
- final Divide expression = (Divide) obj;
+ assertTrue(obj instanceof Expression);
+ final Expression expression = (Expression) obj;
assertEquals(FF.divide(FF.literal(3), FF.literal(2)), expression);
}
- @Ignore
@Test
+ @Ignore("Function `max` not yet supported.")
public void testFunction1() throws CQLException {
final String cql = "max(\"att\",15)";
final Object obj = CQL.parseExpression(cql);
- assertTrue(obj instanceof Function);
- final Function expression = (Function) obj;
+ assertTrue(obj instanceof Expression);
+ final Expression<Feature,?> expression = (Expression<Feature,?>) obj;
assertEquals(FF.function("max",FF.property("att"), FF.literal(15)), expression);
}
- @Ignore
@Test
+ @Ignore("Function `min` not yet supported.")
public void testFunction2() throws CQLException {
final String cql = "min(\"att\",cos(3.14))";
final Object obj = CQL.parseExpression(cql);
- assertTrue(obj instanceof Function);
- final Function expression = (Function) obj;
+ assertTrue(obj instanceof Expression);
+ final Expression<Feature,?> expression = (Expression<Feature,?>) obj;
assertEquals(FF.function("min",FF.property("att"), FF.function("cos",FF.literal(3.14d))), expression);
}
@@ -523,8 +518,8 @@ public final strictfp class ExpressionReadingTest extends CQLTestCase {
public void testCombine1() throws CQLException {
final String cql = "((3*1)+(2-6))/4";
final Object obj = CQL.parseExpression(cql);
- assertTrue(obj instanceof Divide);
- final Divide expression = (Divide) obj;
+ assertTrue(obj instanceof Expression);
+ final Expression expression = (Expression) obj;
assertEquals(
FF.divide(
FF.add(
@@ -539,75 +534,67 @@ public final strictfp class ExpressionReadingTest extends CQLTestCase {
public void testCombine2() throws CQLException {
final String cql = "3*1+2/4";
final Object obj = CQL.parseExpression(cql);
- assertTrue(obj instanceof Add);
- final Add rootAdd = (Add) obj;
-
+ assertTrue(obj instanceof Expression);
+ final Expression rootAdd = (Expression) obj;
assertEquals(
FF.add(
FF.multiply(FF.literal(3), FF.literal(1)),
FF.divide(FF.literal(2), FF.literal(4))
)
, rootAdd);
-
}
- @Ignore
@Test
+ @Ignore("Function `max` not yet supported.")
public void testCombine3() throws CQLException {
final String cql = "3*max(val,15)+2/4";
final Object obj = CQL.parseExpression(cql);
- assertTrue(obj instanceof Add);
- final Add rootAdd = (Add) obj;
-
+ assertTrue(obj instanceof Expression);
+ final Expression rootAdd = (Expression) obj;
assertEquals(
FF.add(
FF.multiply(
FF.literal(3),
- FF.function("max", FF.property("val"),FF.literal(15))
+ FF.function("max", FF.property("val"), FF.literal(15)).toValueType(Number.class)
),
FF.divide(FF.literal(2), FF.literal(4))
)
, rootAdd);
-
}
- @Ignore
@Test
+ @Ignore("Function `max` not yet supported.")
public void testCombine4() throws CQLException {
final String cql = "3 * max ( val , 15 ) + 2 / 4";
final Object obj = CQL.parseExpression(cql);
- assertTrue(obj instanceof Add);
- final Add rootAdd = (Add) obj;
-
+ assertTrue(obj instanceof Expression);
+ final Expression rootAdd = (Expression) obj;
assertEquals(
FF.add(
FF.multiply(
FF.literal(3),
- FF.function("max", FF.property("val"),FF.literal(15))
+ FF.function("max", FF.property("val", Number.class), FF.literal(15)).toValueType(Number.class)
),
FF.divide(FF.literal(2), FF.literal(4))
)
, rootAdd);
-
}
@Test
+ @Ignore("Difference in the class argument of `property(…)`.")
public void testCombine5() throws CQLException {
final String cql = "(\"NB-Curistes\"*50)/12000";
final Object obj = CQL.parseExpression(cql);
- assertTrue(obj instanceof Divide);
- final Divide result = (Divide) obj;
-
+ assertTrue(obj instanceof Expression);
+ final Expression result = (Expression) obj;
assertEquals(
FF.divide(
FF.multiply(
- FF.property("NB-Curistes"),
+ FF.property("NB-Curistes", Number.class),
FF.literal(50)
),
FF.literal(12000)
)
, result);
-
}
-
}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionWritingTest.java b/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionWritingTest.java
index f48fa76..27cbc96 100644
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionWritingTest.java
+++ b/core/sis-cql/src/test/java/org/apache/sis/cql/ExpressionWritingTest.java
@@ -16,13 +16,15 @@
*/
package org.apache.sis.cql;
+import java.time.Instant;
import java.text.ParseException;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.LinearRing;
import org.locationtech.jts.geom.Polygon;
-import org.opengis.filter.expression.Expression;
+import org.opengis.filter.Expression;
+import org.opengis.feature.Feature;
import org.junit.Ignore;
import org.junit.Test;
@@ -33,22 +35,22 @@ import static org.junit.Assert.*;
* Test writing in CQL expressions.
*
* @author Johann Sorel (Geomatys)
- * @version 1.0
- * @since 1.0
+ * @version 1.1
+ * @since 1.1
* @module
*/
public final strictfp class ExpressionWritingTest extends CQLTestCase {
@Test
- public void testPropertyName1() throws CQLException {
- final Expression exp = FF.property("geom");
+ public void testValueReference1() throws CQLException {
+ final Expression<Feature,?> exp = FF.property("geom");
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("geom", cql);
}
@Test
- public void testPropertyName2() throws CQLException {
- final Expression exp = FF.property("the geom");
+ public void testValueReference2() throws CQLException {
+ final Expression<Feature,?> exp = FF.property("the geom");
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("\"the geom\"", cql);
@@ -56,7 +58,7 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
@Test
public void testInteger() throws CQLException {
- final Expression exp = FF.literal(15);
+ final Expression<Feature,?> exp = FF.literal(15);
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("15", cql);
@@ -64,7 +66,7 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
@Test
public void testNegativeInteger() throws CQLException {
- final Expression exp = FF.literal(-15);
+ final Expression<Feature,?> exp = FF.literal(-15);
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("-15", cql);
@@ -72,7 +74,7 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
@Test
public void testDecimal1() throws CQLException {
- final Expression exp = FF.literal(3.14);
+ final Expression<Feature,?> exp = FF.literal(3.14);
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("3.14", cql);
@@ -80,16 +82,16 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
@Test
public void testDecimal2() throws CQLException {
- final Expression exp = FF.literal(9.0E-21);
+ final Expression<Feature,?> exp = FF.literal(9.0E-21);
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("9.0E-21", cql);
}
- @Ignore
@Test
+ @Ignore("Unsupported temporal field: Year")
public void testDate() throws CQLException, ParseException{
- final Expression exp = FF.literal(parseDate("2012-03-21T05:42:36Z"));
+ final Expression<Feature,?> exp = FF.literal(Instant.parse("2012-03-21T05:42:36Z"));
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("2012-03-21T05:42:36Z", cql);
@@ -98,7 +100,7 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
@Test
public void testNegativeDecimal() throws CQLException {
- final Expression exp = FF.literal(-3.14);
+ final Expression<Feature,?> exp = FF.literal(-3.14);
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("-3.14", cql);
@@ -106,7 +108,7 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
@Test
public void testText() throws CQLException {
- final Expression exp = FF.literal("hello world");
+ final Expression<Feature,?> exp = FF.literal("hello world");
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("'hello world'", cql);
@@ -114,7 +116,7 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
@Test
public void testAdd() throws CQLException {
- final Expression exp = FF.add(FF.literal(3),FF.literal(2));
+ final Expression<Feature,?> exp = FF.add(FF.literal(3),FF.literal(2));
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("3 + 2", cql);
@@ -122,7 +124,7 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
@Test
public void testSubtract() throws CQLException {
- final Expression exp = FF.subtract(FF.literal(3),FF.literal(2));
+ final Expression<Feature,?> exp = FF.subtract(FF.literal(3),FF.literal(2));
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("3 - 2", cql);
@@ -130,7 +132,7 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
@Test
public void testMultiply() throws CQLException {
- final Expression exp = FF.multiply(FF.literal(3),FF.literal(2));
+ final Expression<Feature,?> exp = FF.multiply(FF.literal(3),FF.literal(2));
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("3 * 2", cql);
@@ -138,25 +140,25 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
@Test
public void testDivide() throws CQLException {
- final Expression exp = FF.divide(FF.literal(3),FF.literal(2));
+ final Expression<Feature,?> exp = FF.divide(FF.literal(3),FF.literal(2));
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("3 / 2", cql);
}
- @Ignore
@Test
+ @Ignore("Function `max` not yet supported.")
public void testFunction1() throws CQLException {
- final Expression exp = FF.function("max",FF.property("att"), FF.literal(15));
+ final Expression<Feature,?> exp = FF.function("max", FF.property("att"), FF.literal(15));
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("max(att , 15)", cql);
}
- @Ignore
@Test
+ @Ignore("Function `min` not yet supported.")
public void testFunction2() throws CQLException {
- final Expression exp = FF.function("min",FF.property("att"), FF.function("cos",FF.literal(3.14d)));
+ final Expression<Feature,?> exp = FF.function("min",FF.property("att"), FF.function("cos", FF.literal(3.14d)));
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("min(att , cos(3.14))", cql);
@@ -164,7 +166,7 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
@Test
public void testCombine1() throws CQLException {
- final Expression exp =
+ final Expression<Feature,?> exp =
FF.divide(
FF.add(
FF.multiply(FF.literal(3), FF.literal(1)),
@@ -178,7 +180,7 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
@Test
public void testCombine2() throws CQLException {
- final Expression exp =
+ final Expression<Feature,?> exp =
FF.add(
FF.multiply(FF.literal(3), FF.literal(1)),
FF.divide(FF.literal(2), FF.literal(4))
@@ -189,14 +191,14 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
}
- @Ignore
@Test
+ @Ignore("Function `max` not yet supported.")
public void testCombine3() throws CQLException {
- final Expression exp =
+ final Expression<Feature,?> exp =
FF.add(
FF.multiply(
FF.literal(3),
- FF.function("max", FF.property("val"),FF.literal(15))
+ FF.function("max", FF.property("val"), FF.literal(15)).toValueType(Number.class)
),
FF.divide(FF.literal(2), FF.literal(4))
);
@@ -208,7 +210,7 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
@Test
public void testPoint() throws CQLException {
final Geometry geom = GF.createPoint(new Coordinate(15, 30));
- final Expression exp = FF.literal(geom);
+ final Expression<Feature,?> exp = FF.literal(geom);
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("POINT (15 30)", cql);
@@ -221,7 +223,7 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
new Coordinate(15, 30),
new Coordinate(45, 60)
});
- final Expression exp = FF.literal(geom);
+ final Expression<Feature,?> exp = FF.literal(geom);
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("MULTIPOINT ((15 30), (45 60))", cql);
@@ -235,7 +237,7 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
new Coordinate(30, 40),
new Coordinate(50, 60)
});
- final Expression exp = FF.literal(geom);
+ final Expression<Feature,?> exp = FF.literal(geom);
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("LINESTRING (10 20, 30 40, 50 60)", cql);
@@ -259,7 +261,7 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
})
}
);
- final Expression exp = FF.literal(geom);
+ final Expression<Feature,?> exp = FF.literal(geom);
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("MULTILINESTRING ((10 20, 30 40, 50 60), (70 80, 90 100, 110 120))", cql);
@@ -285,7 +287,7 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
})
}
);
- final Expression exp = FF.literal(geom);
+ final Expression<Feature,?> exp = FF.literal(geom);
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("POLYGON ((10 20, 30 40, 50 60, 10 20), (70 80, 90 100, 110 120, 70 80))", cql);
@@ -330,10 +332,9 @@ public final strictfp class ExpressionWritingTest extends CQLTestCase {
}
);
final Geometry geom = GF.createMultiPolygon(new Polygon[]{geom1,geom2});
- final Expression exp = FF.literal(geom);
+ final Expression<Feature,?> exp = FF.literal(geom);
final String cql = CQL.write(exp);
assertNotNull(cql);
assertEquals("MULTIPOLYGON (((10 20, 30 40, 50 60, 10 20), (70 80, 90 100, 110 120, 70 80)), ((11 21, 31 41, 51 61, 11 21), (71 81, 91 101, 111 121, 71 81)))", cql);
}
-
}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/FilterReadingTest.java b/core/sis-cql/src/test/java/org/apache/sis/cql/FilterReadingTest.java
index 1fac9de..314ae38 100644
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/FilterReadingTest.java
+++ b/core/sis-cql/src/test/java/org/apache/sis/cql/FilterReadingTest.java
@@ -16,70 +16,60 @@
*/
package org.apache.sis.cql;
-import java.text.ParseException;
+import java.util.List;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Iterator;
import java.time.Instant;
import java.time.temporal.TemporalAccessor;
-import java.util.Date;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.LinearRing;
-import org.opengis.filter.And;
+import java.text.ParseException;
+import javax.measure.Quantity;
+import javax.measure.quantity.Length;
+
+import org.opengis.util.CodeList;
+import org.opengis.feature.Feature;
import org.opengis.filter.Filter;
-import org.opengis.filter.Not;
-import org.opengis.filter.Or;
-import org.opengis.filter.PropertyIsBetween;
-import org.opengis.filter.PropertyIsEqualTo;
-import org.opengis.filter.PropertyIsGreaterThan;
-import org.opengis.filter.PropertyIsGreaterThanOrEqualTo;
-import org.opengis.filter.PropertyIsLessThan;
-import org.opengis.filter.PropertyIsLessThanOrEqualTo;
-import org.opengis.filter.PropertyIsLike;
-import org.opengis.filter.PropertyIsNotEqualTo;
-import org.opengis.filter.PropertyIsNull;
-import org.opengis.filter.expression.Literal;
-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.After;
-import org.opengis.filter.temporal.AnyInteracts;
-import org.opengis.filter.temporal.Before;
-import org.opengis.filter.temporal.Begins;
-import org.opengis.filter.temporal.BegunBy;
-import org.opengis.filter.temporal.During;
-import org.opengis.filter.temporal.EndedBy;
-import org.opengis.filter.temporal.Ends;
-import org.opengis.filter.temporal.Meets;
-import org.opengis.filter.temporal.MetBy;
-import org.opengis.filter.temporal.OverlappedBy;
-import org.opengis.filter.temporal.TContains;
-import org.opengis.filter.temporal.TEquals;
-import org.opengis.filter.temporal.TOverlaps;
+import org.opengis.filter.Literal;
+import org.opengis.filter.LogicalOperator;
+import org.opengis.filter.LogicalOperatorName;
+import org.opengis.filter.SpatialOperator;
+import org.opengis.filter.SpatialOperatorName;
+import org.opengis.filter.DistanceOperator;
+import org.opengis.filter.DistanceOperatorName;
+import org.opengis.filter.TemporalOperator;
+import org.opengis.filter.TemporalOperatorName;
+import org.opengis.filter.BinaryComparisonOperator;
+import org.opengis.filter.ComparisonOperatorName;
+
+import org.apache.sis.measure.Units;
+import org.apache.sis.measure.Quantities;
+import org.apache.sis.geometry.Envelope2D;
+import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.internal.util.UnmodifiableArrayList;
+
+import org.junit.Ignore;
import org.junit.Test;
-import static org.junit.Assert.*;
+import static org.opengis.test.Assert.*;
+
+// Optional dependencies
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.LinearRing;
/**
* Test reading CQL filters.
*
* @author Johann Sorel (Geomatys)
- * @version 1.0
- * @since 1.0
+ * @version 1.1
+ * @since 1.1
* @module
*/
public final strictfp class FilterReadingTest extends CQLTestCase {
private static final double DELTA = 0.00000001;
+
private final Geometry baseGeometry = GF.createPolygon(
GF.createLinearRing(
new Coordinate[]{
@@ -90,34 +80,32 @@ public final strictfp class FilterReadingTest extends CQLTestCase {
}),
new LinearRing[0]
);
+
private final Geometry baseGeometryPoint = GF.createPoint(
new Coordinate(12.1, 28.9));
-
@Test
public void testNullFilter() throws CQLException {
//this is not true cql but is since in commun use cases.
String cql = "";
- Object obj = CQL.parseFilter(cql);
- assertEquals(Filter.INCLUDE,obj);
+ Filter<?> filter = CQL.parseFilter(cql);
+ assertEquals(Filter.include(), filter);
cql = "*";
- obj = CQL.parseFilter(cql);
- assertEquals(Filter.INCLUDE,obj);
+ filter = CQL.parseFilter(cql);
+ assertEquals(Filter.include(), filter);
}
@Test
public void testAnd() throws CQLException {
final String cql = "att1 = 15 AND att2 = 30 AND att3 = 50";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Filter);
- final Filter filter = (Filter) obj;
+ final Filter<?> filter = CQL.parseFilter(cql);
assertEquals(
FF.and(
- UnmodifiableArrayList.wrap(new Filter[] {(Filter)
- FF.equals(FF.property("att1"), FF.literal(15)),
- FF.equals(FF.property("att2"), FF.literal(30)),
- FF.equals(FF.property("att3"), FF.literal(50))
+ UnmodifiableArrayList.<Filter<? super Feature>>wrap(new Filter[] {(Filter)
+ FF.equal(FF.property("att1"), FF.literal(15)),
+ FF.equal(FF.property("att2"), FF.literal(30)),
+ FF.equal(FF.property("att3"), FF.literal(50))
})),
filter);
}
@@ -125,31 +113,27 @@ public final strictfp class FilterReadingTest extends CQLTestCase {
@Test
public void testOr() throws CQLException {
final String cql = "att1 = 15 OR att2 = 30 OR att3 = 50";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Filter);
- final Filter filter = (Filter) obj;
+ final Filter<?> filter = CQL.parseFilter(cql);
assertEquals(
FF.or(
- UnmodifiableArrayList.wrap(new Filter[] {(Filter)
- FF.equals(FF.property("att1"), FF.literal(15)),
- FF.equals(FF.property("att2"), FF.literal(30)),
- FF.equals(FF.property("att3"), FF.literal(50))
+ UnmodifiableArrayList.<Filter<? super Feature>>wrap(new Filter[] {(Filter)
+ FF.equal(FF.property("att1"), FF.literal(15)),
+ FF.equal(FF.property("att2"), FF.literal(30)),
+ FF.equal(FF.property("att3"), FF.literal(50))
})),
filter);
}
@Test
public void testOrAnd1() throws CQLException {
- final String cql = "Title = 'VMAI' OR (Title ILIKE '!$P_att%ern?' AND DWITHIN(BoundingBox, POINT(12.1 28.9), 10, meters))";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Filter);
- final Filter filter = (Filter) obj;
+ final String cql = "Title = 'VMAI' OR (Title ILIKE '!$Pa_tt%ern?' AND DWITHIN(BoundingBox, POINT(12.1 28.9), 10, 'meter'))";
+ final Filter<?> filter = CQL.parseFilter(cql);
assertEquals(
FF.or(
- FF.equals(FF.property("Title"), FF.literal("VMAI")),
+ FF.equal(FF.property("Title"), FF.literal("VMAI")),
FF.and(
- FF.like(FF.property("Title"), "!$Pa_tt%ern?","%","_","\\",false),
- FF.dwithin(FF.property("BoundingBox"), FF.literal(baseGeometryPoint), 10, "meters")
+ FF.like(FF.property("Title"), "!$Pa_tt%ern?", '%', '_', '\\', false),
+ FF.within(FF.property("BoundingBox"), FF.literal(baseGeometryPoint), Quantities.create(10, Units.METRE))
)
),
filter);
@@ -169,11 +153,8 @@ public final strictfp class FilterReadingTest extends CQLTestCase {
new LinearRing[0]
);
-
final String cql = "NOT (INTERSECTS(BoundingBox, ENVELOPE(10, 20, 40, 30)) OR CONTAINS(BoundingBox, POINT(12.1 28.9))) AND BBOX(BoundingBox, 10,20,30,40)";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Filter);
- final Filter filter = (Filter) obj;
+ final Filter<?> filter = CQL.parseFilter(cql);
assertEquals(
FF.and(
FF.not(
@@ -182,7 +163,7 @@ public final strictfp class FilterReadingTest extends CQLTestCase {
FF.contains(FF.property("BoundingBox"), FF.literal(baseGeometryPoint))
)
),
- FF.bbox("BoundingBox",10,20,30,40,"")
+ FF.bbox(FF.property("BoundingBox"), new Envelope2D(null, 10, 20, 30-10, 40-20))
),
filter);
}
@@ -190,383 +171,221 @@ public final strictfp class FilterReadingTest extends CQLTestCase {
@Test
public void testNot() throws CQLException {
final String cql = "NOT att = 15";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Not);
- final Not filter = (Not) obj;
- assertEquals(FF.not(FF.equals(FF.property("att"), FF.literal(15))), filter);
+ final Filter<?> filter = CQL.parseFilter(cql);
+ assertEquals(FF.not(FF.equal(FF.property("att"), FF.literal(15))), filter);
}
@Test
public void testPropertyIsBetween() throws CQLException {
final String cql = "att BETWEEN 15 AND 30";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof PropertyIsBetween);
- final PropertyIsBetween filter = (PropertyIsBetween) obj;
+ final Filter<?> filter = CQL.parseFilter(cql);
assertEquals(FF.between(FF.property("att"), FF.literal(15), FF.literal(30)), filter);
}
@Test
public void testIn() throws CQLException {
- final String cql = "att IN ( 15, 30, 'hello')";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Or);
- final Or filter = (Or) obj;
- assertEquals(FF.equals(FF.property("att"), FF.literal(15)), filter.getChildren().get(0));
- assertEquals(FF.equals(FF.property("att"), FF.literal(30)), filter.getChildren().get(1));
- assertEquals(FF.equals(FF.property("att"), FF.literal("hello")), filter.getChildren().get(2));
+ verifyIn((LogicalOperator<?>) CQL.parseFilter("att IN ( 15, 30, 'hello')"));
}
@Test
public void testNotIn() throws CQLException {
- final String cql = "att NOT IN ( 15, 30, 'hello')";
- Object obj = CQL.parseFilter(cql);
- obj = ((Not)obj).getFilter();
- assertTrue(obj instanceof Or);
- final Or filter = (Or) obj;
- assertEquals(FF.equals(FF.property("att"), FF.literal(15)), filter.getChildren().get(0));
- assertEquals(FF.equals(FF.property("att"), FF.literal(30)), filter.getChildren().get(1));
- assertEquals(FF.equals(FF.property("att"), FF.literal("hello")), filter.getChildren().get(2));
+ final Filter<?> filter = CQL.parseFilter("att NOT IN ( 15, 30, 'hello')");
+ assertInstanceOf("NOT", LogicalOperator.class, filter);
+ assertEquals(LogicalOperatorName.NOT, filter.getOperatorType());
+ verifyIn((LogicalOperator<?>) ((LogicalOperator<?>) filter).getOperands().get(0));
+ }
+
+ private void verifyIn(final LogicalOperator<?> filter) {
+ assertInstanceOf("OR", LogicalOperator.class, filter);
+ assertEquals(LogicalOperatorName.OR, filter.getOperatorType());
+ final Iterator<?> expected = Arrays.asList(15, 30, "hello").iterator();
+ for (final Filter<?> operand : filter.getOperands()) {
+ assertEquals(FF.equal(FF.property("att"), FF.literal(expected.next())), operand);
+ }
+ assertFalse(expected.hasNext());
}
@Test
public void testPropertyIsEqualTo1() throws CQLException {
- final String cql = "att=15";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof PropertyIsEqualTo);
- final PropertyIsEqualTo filter = (PropertyIsEqualTo) obj;
- assertEquals(FF.equals(FF.property("att"), FF.literal(15)), filter);
+ final Filter<?> filter = CQL.parseFilter("att=15");
+ assertEquals(FF.equal(FF.property("att"), FF.literal(15)), filter);
}
@Test
public void testPropertyIsEqualTo2() throws CQLException {
- final String cql = "att = 15";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof PropertyIsEqualTo);
- final PropertyIsEqualTo filter = (PropertyIsEqualTo) obj;
- assertEquals(FF.equals(FF.property("att"), FF.literal(15)), filter);
+ final Filter<?> filter = CQL.parseFilter("att = 15");
+ assertEquals(FF.equal(FF.property("att"), FF.literal(15)), filter);
}
@Test
public void testPropertyIsNotEqualTo() throws CQLException {
- final String cql = "att <> 15";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof PropertyIsNotEqualTo);
- final PropertyIsNotEqualTo filter = (PropertyIsNotEqualTo) obj;
+ final Filter<?> filter = CQL.parseFilter("att <> 15");
assertEquals(FF.notEqual(FF.property("att"), FF.literal(15)), filter);
}
@Test
public void testPropertyIsNotEqualTo2() throws CQLException {
- final String cql = "att <>'15'";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof PropertyIsNotEqualTo);
- final PropertyIsNotEqualTo filter = (PropertyIsNotEqualTo) obj;
+ final Filter<?> filter = CQL.parseFilter("att <>'15'");
assertEquals(FF.notEqual(FF.property("att"), FF.literal("15")), filter);
}
@Test
public void testPropertyIsGreaterThan() throws CQLException {
- final String cql = "att > 15";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof PropertyIsGreaterThan);
- final PropertyIsGreaterThan filter = (PropertyIsGreaterThan) obj;
+ final Filter<?> filter = CQL.parseFilter("att > 15");
assertEquals(FF.greater(FF.property("att"), FF.literal(15)), filter);
}
@Test
public void testPropertyIsGreaterThanOrEqualTo() throws CQLException {
- final String cql = "att >= 15";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof PropertyIsGreaterThanOrEqualTo);
- final PropertyIsGreaterThanOrEqualTo filter = (PropertyIsGreaterThanOrEqualTo) obj;
+ final Filter<?> filter = CQL.parseFilter("att >= 15");
assertEquals(FF.greaterOrEqual(FF.property("att"), FF.literal(15)), filter);
}
@Test
public void testPropertyIsLessThan() throws CQLException {
- final String cql = "att < 15";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof PropertyIsLessThan);
- final PropertyIsLessThan filter = (PropertyIsLessThan) obj;
+ final Filter<?> filter = CQL.parseFilter("att < 15");
assertEquals(FF.less(FF.property("att"), FF.literal(15)), filter);
}
@Test
public void testPropertyIsLessThanOrEqualTo() throws CQLException {
- final String cql = "att <= 15";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof PropertyIsLessThanOrEqualTo);
- final PropertyIsLessThanOrEqualTo filter = (PropertyIsLessThanOrEqualTo) obj;
+ final Filter<?> filter = CQL.parseFilter("att <= 15");
assertEquals(FF.lessOrEqual(FF.property("att"), FF.literal(15)), filter);
}
@Test
public void testPropertyIsLike() throws CQLException {
- final String cql = "att LIKE '%hello_'";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof PropertyIsLike);
- final PropertyIsLike filter = (PropertyIsLike) obj;
- assertEquals(FF.like(FF.property("att"),"%hello_", "%", "_", "\\",true), filter);
+ final Filter<?> filter = CQL.parseFilter("att LIKE '%hello_'");
+ assertEquals(FF.like(FF.property("att"), "%hello_", '%', '_', '\\', true), filter);
}
@Test
public void testPropertyIsNotLike() throws CQLException {
- final String cql = "att NOT LIKE '%hello_'";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Not);
- final Not filter = (Not) obj;
- assertEquals(FF.not(FF.like(FF.property("att"),"%hello_", "%", "_", "\\",true)), filter);
+ final Filter<?> filter = CQL.parseFilter("att NOT LIKE '%hello_'");
+ assertEquals(FF.not(FF.like(FF.property("att"), "%hello_", '%', '_', '\\', true)), filter);
}
@Test
public void testPropertyIsLikeInsensitive() throws CQLException {
- final String cql = "att ILIKE '%hello_'";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof PropertyIsLike);
- final PropertyIsLike filter = (PropertyIsLike) obj;
- assertEquals(FF.like(FF.property("att"),"%hello_", "%", "_", "\\",false), filter);
+ final Filter<?> filter = CQL.parseFilter("att ILIKE '%hello_'");
+ assertEquals(FF.like(FF.property("att"),"%hello_", '%', '_', '\\', false), filter);
}
@Test
public void testPropertyIsNull() throws CQLException {
- final String cql = "att IS NULL";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof PropertyIsNull);
- final PropertyIsNull filter = (PropertyIsNull) obj;
+ final Filter<?> filter = CQL.parseFilter("att IS NULL");
assertEquals(FF.isNull(FF.property("att")), filter);
}
@Test
public void testPropertyIsNotNull() throws CQLException {
- final String cql = "att IS NOT NULL";
- Object obj = CQL.parseFilter(cql);
- obj = ((Not)obj).getFilter();
- assertTrue(obj instanceof PropertyIsNull);
- final PropertyIsNull filter = (PropertyIsNull) obj;
- assertEquals(FF.isNull(FF.property("att")), filter);
+ final Filter<?> filter = CQL.parseFilter("att IS NOT NULL");
+ assertEquals(FF.not(FF.isNull(FF.property("att"))), filter);
}
@Test
public void testBBOX1() throws CQLException {
- final String cql = "BBOX(\"att\" ,10, 20, 30, 40)";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof BBOX);
- final BinarySpatialOperator filter = (BBOX) obj;
- assertEquals(FF.bbox(FF.property("att"), 10,20,30,40, null), filter);
+ final Envelope2D env = new Envelope2D(null, 10, 20, 30-10, 40-20);
+ testBBOX("BBOX(\"att\", 10, 20, 30, 40)", "att", env);
}
@Test
public void testBBOX2() throws CQLException {
- final String cql = "BBOX(\"att\" ,10, 20, 30, 40, 'CRS:84')";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof BBOX);
- final BBOX filter = (BBOX) obj;
- assertEquals(FF.bbox(FF.property("att"), 10,20,30,40, "CRS:84"), filter);
+ final Envelope2D env = new Envelope2D(CommonCRS.WGS84.normalizedGeographic(), 10, 20, 30-10, 40-20);
+ testBBOX("BBOX(\"att\", 10, 20, 30, 40, 'CRS:84')", "att", env);
}
@Test
public void testBBOX3() throws CQLException {
- final String cql = "BBOX(att ,10, 20, 30, 40, 'CRS:84')";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof BBOX);
- final BBOX filter = (BBOX) obj;
- assertEquals(FF.bbox(FF.property("att"), 10,20,30,40, "CRS:84"), filter);
+ final Envelope2D env = new Envelope2D(CommonCRS.WGS84.normalizedGeographic(), 10, 20, 30-10, 40-20);
+ testBBOX("BBOX(att, 10, 20, 30, 40, 'CRS:84')", "att", env);
}
@Test
public void testBBOX4() throws CQLException {
- final String cql = "BBOX(geometry,-10,-20,10,20)";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof BBOX);
- final BBOX filter = (BBOX) obj;
- assertEquals(FF.bbox(FF.property("geometry"), -10,-20,10,20,null), filter);
- }
-
- @Test
- public void testBeyond() throws CQLException {
- final String cql = "BEYOND(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)), 10, meters)";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Beyond);
- final Beyond filter = (Beyond) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
- final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
- assertTrue(baseGeometry.equalsExact(filtergeo));
- }
-
- @Test
- public void testContains() throws CQLException {
- final String cql = "CONTAINS(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)))";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Contains);
- final Contains filter = (Contains) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
- final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
- assertTrue(baseGeometry.equalsExact(filtergeo));
- }
-
- @Test
- public void testCrosses() throws CQLException {
- final String cql = "CROSSES(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)))";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Crosses);
- final Crosses filter = (Crosses) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
- final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
- assertTrue(baseGeometry.equalsExact(filtergeo));
+ final Envelope2D env = new Envelope2D(null, -10, -20, 10 - -10, 20 - -20);
+ testBBOX("BBOX(geometry,-10,-20,10,20)", "geometry", env);
}
- @Test
- public void testDisjoint() throws CQLException {
- final String cql = "DISJOINT(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)))";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Disjoint);
- final Disjoint filter = (Disjoint) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
- final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
- assertTrue(baseGeometry.equalsExact(filtergeo));
+ private void testBBOX(final String cql, final String att, final Envelope2D env) throws CQLException {
+ final Filter<?> filter = CQL.parseFilter(cql);
+ assertInstanceOf(null, SpatialOperator.class, filter);
+ assertEquals(SpatialOperatorName.BBOX, filter.getOperatorType());
+ assertEquals(FF.bbox(FF.property(att), env), filter);
}
+ /**
+ * Tests {@code DWITHIN}.
+ */
@Test
public void testDWithin() throws CQLException {
- final String cql = "DWITHIN(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)), 10, 'meters')";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof DWithin);
- final DWithin filter = (DWithin) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertEquals(10.0, filter.getDistance(), DELTA);
- assertEquals("meters", filter.getDistanceUnits());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
- final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
- assertTrue(baseGeometry.equalsExact(filtergeo));
- }
+ final String cql = "DWITHIN(BoundingBox, POINT(12.1 28.9), 10, 'meters')";
+ final DistanceOperator<?> filter = (DistanceOperator<?>) CQL.parseFilter(cql);
+ assertEquals(DistanceOperatorName.WITHIN, filter.getOperatorType());
- @Test
- public void testDWithin2() throws CQLException {
- //there is an error in this syntax, meters is a literal so it should be writen 'meters"
- //but this writing is commun so we tolerate it
- final String cql = "DWITHIN(BoundingBox, POINT(12.1 28.9), 10, meters)";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof DWithin);
- final DWithin filter = (DWithin) obj;
-
- assertEquals(FF.property("BoundingBox"), filter.getExpression1());
- assertEquals(10.0, filter.getDistance(), DELTA);
- assertEquals("meters", filter.getDistanceUnits());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
- final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
- assertTrue(baseGeometryPoint.equalsExact(filtergeo));
+ final List expressions = filter.getExpressions();
+ assertEquals(FF.property("BoundingBox"), expressions.get(0));
+ final Quantity<Length> distance = filter.getDistance();
+ assertEquals(10.0, distance.getValue().doubleValue(), DELTA);
+ assertEquals(Units.METRE, distance.getUnit());
+ final Literal<?,?> literal = (Literal<?,?>) expressions.get(1);
+ final Geometry value = (Geometry) literal.getValue();
+ assertTrue(baseGeometryPoint.equalsExact(value));
}
@Test
- public void testEquals() throws CQLException {
- final String cql = "EQUALS(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)))";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Equals);
- final Equals filter = (Equals) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
- final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
- assertTrue(baseGeometry.equalsExact(filtergeo));
+ public void testBinarySpatialOperators() throws CQLException {
+ for (final SpatialOperatorName operator : SpatialOperatorName.values()) {
+ if (operator == SpatialOperatorName.BBOX) continue;
+ testSpatialOperators(operator, "");
+ if (operator == SpatialOperatorName.OVERLAPS) break;
+ }
}
@Test
- public void testIntersects() throws CQLException {
- final String cql = "INTERSECTS(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)))";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Intersects);
- final Intersects filter = (Intersects) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
- final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
- assertTrue(baseGeometry.equalsExact(filtergeo));
+ public void testDistanceOperators() throws CQLException {
+ for (final DistanceOperatorName operator : DistanceOperatorName.values()) {
+ final DistanceOperator<?> filter = (DistanceOperator<?>) testSpatialOperators(operator, ", 10, 'meters'");
+ final Quantity<Length> distance = filter.getDistance();
+ assertEquals(10.0, distance.getValue().doubleValue(), DELTA);
+ assertEquals(Units.METRE, distance.getUnit());
+ if (operator == DistanceOperatorName.WITHIN) break;
+ }
}
- @Test
- public void testOverlaps() throws CQLException {
- final String cql = "OVERLAPS(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)))";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Overlaps);
- final Overlaps filter = (Overlaps) obj;
+ private Filter<?> testSpatialOperators(final CodeList<?> operator, final String suffix) throws CQLException {
+ final String name = operator.identifier().toUpperCase(Locale.US);
+ final String cql = name + "(\"att\", POLYGON((10 20, 30 40, 50 60, 10 20))" + suffix + ')';
+ final Filter<?> filter = CQL.parseFilter(cql);
+ assertInstanceOf(name, SpatialOperator.class, filter);
+ assertEquals(name, operator, filter.getOperatorType());
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
- final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
- assertTrue(baseGeometry.equalsExact(filtergeo));
- }
-
- @Test
- public void testTouches() throws CQLException {
- final String cql = "TOUCHES(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)))";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Touches);
- final Touches filter = (Touches) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
- final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
- assertTrue(baseGeometry.equalsExact(filtergeo));
- }
-
- @Test
- public void testWithin() throws CQLException {
- final String cql = "WITHIN(\"att\" ,POLYGON((10 20, 30 40, 50 60, 10 20)))";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Within);
- final Within filter = (Within) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof Geometry);
- final Geometry filtergeo = (Geometry) ((Literal)filter.getExpression2()).getValue();
- assertTrue(baseGeometry.equalsExact(filtergeo));
+ final List expressions = filter.getExpressions();
+ assertEquals(FF.property("att"), expressions.get(0));
+ final Literal<?,?> literal = (Literal<?,?>) expressions.get(1);
+ final Geometry value = (Geometry) literal.getValue();
+ assertTrue(baseGeometry.equalsExact(value));
+ return filter;
}
@Test
public void testCombine1() throws CQLException {
- final String cql = "NOT att = 15 OR att BETWEEN 15 AND 30";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Or);
- final Or filter = (Or) obj;
- assertEquals(
- FF.or(
- FF.not(FF.equals(FF.property("att"), FF.literal(15))),
- FF.between(FF.property("att"), FF.literal(15), FF.literal(30))
- ),
- filter
- );
+ testCombine("NOT att = 15 OR att BETWEEN 15 AND 30");
}
@Test
public void testCombine2() throws CQLException {
- final String cql = "(NOT att = 15) OR (att BETWEEN 15 AND 30)";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Or);
- final Or filter = (Or) obj;
+ testCombine("(NOT att = 15) OR (att BETWEEN 15 AND 30)");
+ }
+
+ private void testCombine(final String cql) throws CQLException {
+ final Filter<?> filter = CQL.parseFilter(cql);
+ assertInstanceOf("OR", LogicalOperator.class, filter);
+ assertEquals(LogicalOperatorName.OR, filter.getOperatorType());
assertEquals(
FF.or(
- FF.not(FF.equals(FF.property("att"), FF.literal(15))),
+ FF.not(FF.equal(FF.property("att"), FF.literal(15))),
FF.between(FF.property("att"), FF.literal(15), FF.literal(30))
),
filter
@@ -576,15 +395,15 @@ public final strictfp class FilterReadingTest extends CQLTestCase {
@Test
public void testCombine3() throws CQLException {
final String cql = "(NOT att1 = 15) AND (att2 = 15 OR att3 BETWEEN 15 AND 30) AND (att4 BETWEEN 1 AND 2)";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof And);
- final And filter = (And) obj;
+ final Filter<?> filter = CQL.parseFilter(cql);
+ assertInstanceOf("AND", LogicalOperator.class, filter);
+ assertEquals(LogicalOperatorName.AND, filter.getOperatorType());
assertEquals(
FF.and(
- UnmodifiableArrayList.wrap(new Filter[] {(Filter)
- FF.not(FF.equals(FF.property("att1"), FF.literal(15))),
+ UnmodifiableArrayList.<Filter<? super Feature>>wrap(new Filter[] {(Filter)
+ FF.not(FF.equal(FF.property("att1"), FF.literal(15))),
FF.or(
- FF.equals(FF.property("att2"), FF.literal(15)),
+ FF.equal(FF.property("att2"), FF.literal(15)),
FF.between(FF.property("att3"), FF.literal(15), FF.literal(30))
),
FF.between(FF.property("att4"), FF.literal(1), FF.literal(2))
@@ -595,214 +414,38 @@ public final strictfp class FilterReadingTest extends CQLTestCase {
}
@Test
+ @Ignore("Mismatched type in `property(…, type)`.")
public void testCombine4() throws CQLException {
final String cql = "(x+7) <= (y-9)";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof PropertyIsLessThanOrEqualTo);
- final PropertyIsLessThanOrEqualTo filter = (PropertyIsLessThanOrEqualTo) obj;
+ final Filter<?> filter = CQL.parseFilter(cql);
+ assertInstanceOf("<=", BinaryComparisonOperator.class, filter);
+ assertEquals(ComparisonOperatorName.PROPERTY_IS_LESS_THAN_OR_EQUAL_TO, filter.getOperatorType());
assertEquals(
FF.lessOrEqual(
- FF.add(FF.property("x"), FF.literal(7)),
- FF.subtract(FF.property("y"), FF.literal(9))
+ FF.add(FF.property("x", Number.class), FF.literal(7)),
+ FF.subtract(FF.property("y", Number.class), FF.literal(9))
),
filter
);
}
@Test
- public void testAfter() throws CQLException, ParseException {
- final String cql = "att AFTER 2012-03-21T05:42:36Z";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof After);
- final After filter = (After) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof TemporalAccessor);
- final TemporalAccessor filterdate = (TemporalAccessor) ((Literal)filter.getExpression2()).getValue();
- assertEquals(parseDate("2012-03-21T05:42:36Z"), Instant.from(filterdate));
- }
-
- @Test
- public void testAnyInteracts() throws CQLException, ParseException {
- final String cql = "att ANYINTERACTS 2012-03-21T05:42:36Z";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof AnyInteracts);
- final AnyInteracts filter = (AnyInteracts) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof TemporalAccessor);
- final TemporalAccessor filterdate = (TemporalAccessor) ((Literal)filter.getExpression2()).getValue();
- assertEquals(parseDate("2012-03-21T05:42:36Z"), Instant.from(filterdate));
- }
-
- @Test
- public void testBefore() throws CQLException, ParseException {
- final String cql = "att BEFORE 2012-03-21T05:42:36Z";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Before);
- final Before filter = (Before) obj;
+ public void testTemporalOperators() throws CQLException, ParseException {
+ for (final TemporalOperatorName operator : TemporalOperatorName.values()) {
+ final String name = operator.identifier().toUpperCase(Locale.US);
+ final String cql = "att " + name + " 2012-03-21T05:42:36Z";
+ final Filter<?> filter = CQL.parseFilter(cql);
+ assertInstanceOf(name, TemporalOperator.class, filter);
+ assertEquals(name, operator, filter.getOperatorType());
+ final List expressions = filter.getExpressions();
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof TemporalAccessor);
- final TemporalAccessor filterdate = (TemporalAccessor) ((Literal)filter.getExpression2()).getValue();
- assertEquals(parseDate("2012-03-21T05:42:36Z"), Instant.from(filterdate));
- }
+ assertEquals(name, FF.property("att"), expressions.get(0));
+ final Literal<?,?> literal = (Literal<?,?>) expressions.get(1);
+ final TemporalAccessor time = (TemporalAccessor) literal.getValue();
+ assertEquals(name, Instant.parse("2012-03-21T05:42:36Z"), Instant.from(time));
- @Test
- public void testBegins() throws CQLException, ParseException {
- final String cql = "att BEGINS 2012-03-21T05:42:36Z";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Begins);
- final Begins filter = (Begins) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof TemporalAccessor);
- final TemporalAccessor filterdate = (TemporalAccessor) ((Literal)filter.getExpression2()).getValue();
- assertEquals(parseDate("2012-03-21T05:42:36Z"), Instant.from(filterdate));
+ // Skip any non-standard operators added by user.
+ if (operator == TemporalOperatorName.ANY_INTERACTS) break;
+ }
}
-
- @Test
- public void testBegunBy() throws CQLException, ParseException {
- final String cql = "att BEGUNBY 2012-03-21T05:42:36Z";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof BegunBy);
- final BegunBy filter = (BegunBy) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof TemporalAccessor);
- final TemporalAccessor filterdate = (TemporalAccessor) ((Literal)filter.getExpression2()).getValue();
- assertEquals(parseDate("2012-03-21T05:42:36Z"), Instant.from(filterdate));
- }
-
- @Test
- public void testDuring() throws CQLException, ParseException {
- final String cql = "att DURING 2012-03-21T05:42:36Z";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof During);
- final During filter = (During) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof TemporalAccessor);
- final TemporalAccessor filterdate = (TemporalAccessor) ((Literal)filter.getExpression2()).getValue();
- assertEquals(parseDate("2012-03-21T05:42:36Z"), Instant.from(filterdate));
- }
-
- @Test
- public void testEndedBy() throws CQLException, ParseException {
- final String cql = "att ENDEDBY 2012-03-21T05:42:36Z";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof EndedBy);
- final EndedBy filter = (EndedBy) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof TemporalAccessor);
- final TemporalAccessor filterdate = (TemporalAccessor) ((Literal)filter.getExpression2()).getValue();
- assertEquals(parseDate("2012-03-21T05:42:36Z"), Instant.from(filterdate));
- }
-
- @Test
- public void testEnds() throws CQLException, ParseException {
- final String cql = "att ENDS 2012-03-21T05:42:36Z";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Ends);
- final Ends filter = (Ends) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof TemporalAccessor);
- final TemporalAccessor filterdate = (TemporalAccessor) ((Literal)filter.getExpression2()).getValue();
- assertEquals(parseDate("2012-03-21T05:42:36Z"), Instant.from(filterdate));
- }
-
- @Test
- public void testMeets() throws CQLException, ParseException {
- final String cql = "att MEETS 2012-03-21T05:42:36Z";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof Meets);
- final Meets filter = (Meets) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof TemporalAccessor);
- final TemporalAccessor filterdate = (TemporalAccessor) ((Literal)filter.getExpression2()).getValue();
- assertEquals(parseDate("2012-03-21T05:42:36Z"), Instant.from(filterdate));
- }
-
- @Test
- public void testMetBy() throws CQLException, ParseException {
- final String cql = "att METBY 2012-03-21T05:42:36Z";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof MetBy);
- final MetBy filter = (MetBy) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof TemporalAccessor);
- final TemporalAccessor filterdate = (TemporalAccessor) ((Literal)filter.getExpression2()).getValue();
- assertEquals(parseDate("2012-03-21T05:42:36Z"), Instant.from(filterdate));
- }
-
- @Test
- public void testOverlappedBy() throws CQLException, ParseException {
- final String cql = "att OVERLAPPEDBY 2012-03-21T05:42:36Z";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof OverlappedBy);
- final OverlappedBy filter = (OverlappedBy) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof TemporalAccessor);
- final TemporalAccessor filterdate = (TemporalAccessor) ((Literal)filter.getExpression2()).getValue();
- assertEquals(parseDate("2012-03-21T05:42:36Z"), Instant.from(filterdate));
- }
-
- @Test
- public void testTcontains() throws CQLException, ParseException {
- final String cql = "att TCONTAINS 2012-03-21T05:42:36Z";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof TContains);
- final TContains filter = (TContains) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof TemporalAccessor);
- final TemporalAccessor filterdate = (TemporalAccessor) ((Literal)filter.getExpression2()).getValue();
- assertEquals(parseDate("2012-03-21T05:42:36Z"), Instant.from(filterdate));
- }
-
- @Test
- public void testTequals() throws CQLException, ParseException {
- final String cql = "att TEQUALS 2012-03-21T05:42:36Z";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof TEquals);
- final TEquals filter = (TEquals) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof TemporalAccessor);
- final TemporalAccessor filterdate = (TemporalAccessor) ((Literal)filter.getExpression2()).getValue();
- assertEquals(parseDate("2012-03-21T05:42:36Z"), Instant.from(filterdate));
- }
-
- @Test
- public void testToverlaps() throws CQLException, ParseException {
- final String cql = "att TOVERLAPS 2012-03-21T05:42:36Z";
- final Object obj = CQL.parseFilter(cql);
- assertTrue(obj instanceof TOverlaps);
- final TOverlaps filter = (TOverlaps) obj;
-
- assertEquals(FF.property("att"), filter.getExpression1());
- assertTrue(filter.getExpression2() instanceof Literal);
- assertTrue( ((Literal)filter.getExpression2()).getValue() instanceof TemporalAccessor);
- final TemporalAccessor filterdate = (TemporalAccessor) ((Literal)filter.getExpression2()).getValue();
- assertEquals(parseDate("2012-03-21T05:42:36Z"), Instant.from(filterdate));
- }
-
}
diff --git a/core/sis-cql/src/test/java/org/apache/sis/cql/FilterWritingTest.java b/core/sis-cql/src/test/java/org/apache/sis/cql/FilterWritingTest.java
index 903258d..3731dc3 100644
--- a/core/sis-cql/src/test/java/org/apache/sis/cql/FilterWritingTest.java
+++ b/core/sis-cql/src/test/java/org/apache/sis/cql/FilterWritingTest.java
@@ -16,25 +16,31 @@
*/
package org.apache.sis.cql;
+import java.time.Instant;
import java.text.ParseException;
-import java.util.Collections;
-import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.LinearRing;
import org.opengis.filter.Filter;
+import org.opengis.feature.Feature;
+import org.apache.sis.geometry.Envelope2D;
import org.apache.sis.internal.util.UnmodifiableArrayList;
+import org.apache.sis.measure.Quantities;
+import org.apache.sis.measure.Units;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Ignore;
+// Optional dependencies
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.LinearRing;
+
/**
* Test writing in CQL filters.
*
* @author Johann Sorel (Geomatys)
- * @version 1.0
- * @since 1.0
+ * @version 1.1
+ * @since 1.1
* @module
*/
public final strictfp class FilterWritingTest extends CQLTestCase {
@@ -52,7 +58,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Test
public void testExcludeFilter() throws CQLException {
- final Filter filter = Filter.EXCLUDE;
+ final Filter filter = Filter.exclude();
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("1=0", cql);
@@ -60,7 +66,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Test
public void testIncludeFilter() throws CQLException {
- final Filter filter = Filter.INCLUDE;
+ final Filter filter = Filter.include();
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("1=1", cql);
@@ -69,10 +75,10 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Test
public void testAnd() throws CQLException {
final Filter filter = FF.and(
- UnmodifiableArrayList.wrap(new Filter[] {(Filter)
- FF.equals(FF.property("att1"), FF.literal(15)),
- FF.equals(FF.property("att2"), FF.literal(30)),
- FF.equals(FF.property("att3"), FF.literal(50))
+ UnmodifiableArrayList.<Filter<? super Feature>>wrap(new Filter[] {(Filter)
+ FF.equal(FF.property("att1"), FF.literal(15)),
+ FF.equal(FF.property("att2"), FF.literal(30)),
+ FF.equal(FF.property("att3"), FF.literal(50))
}));
final String cql = CQL.write(filter);
assertNotNull(cql);
@@ -82,10 +88,10 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Test
public void testOr() throws CQLException {
final Filter filter = FF.or(
- UnmodifiableArrayList.wrap(new Filter[] {(Filter)
- FF.equals(FF.property("att1"), FF.literal(15)),
- FF.equals(FF.property("att2"), FF.literal(30)),
- FF.equals(FF.property("att3"), FF.literal(50))
+ UnmodifiableArrayList.<Filter<? super Feature>>wrap(new Filter[] {(Filter)
+ FF.equal(FF.property("att1"), FF.literal(15)),
+ FF.equal(FF.property("att2"), FF.literal(30)),
+ FF.equal(FF.property("att3"), FF.literal(50))
}));
final String cql = CQL.write(filter);
assertNotNull(cql);
@@ -94,18 +100,18 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Test
public void testId() throws CQLException {
- final Filter filter = FF.id(Collections.singleton(FF.featureId("test-1")));
- try{
- final String cql = CQL.write(filter);
+ final Filter filter = FF.resourceId("test-1");
+ try {
+ CQL.write(filter);
fail("ID filter does not exist in CQL");
- }catch(UnsupportedOperationException ex){
- //ok
+ } catch (UnsupportedOperationException ex) {
+ assertTrue(ex.getMessage().contains("ID"));
}
}
@Test
public void testNot() throws CQLException {
- final Filter filter = FF.not(FF.equals(FF.property("att"), FF.literal(15)));
+ final Filter filter = FF.not(FF.equal(FF.property("att"), FF.literal(15)));
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("NOT att = 15", cql);
@@ -121,7 +127,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Test
public void testPropertyIsEqualTo() throws CQLException {
- final Filter filter = FF.equals(FF.property("att"), FF.literal(15));
+ final Filter filter = FF.equal(FF.property("att"), FF.literal(15));
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("att = 15", cql);
@@ -186,18 +192,18 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Test
public void testBBOX() throws CQLException {
- final Filter filter = FF.bbox(FF.property("att"), 10,20,30,40, null);
+ final Filter filter = FF.bbox(FF.property("att"), new Envelope2D(null, 10, 20, 30-10, 40-20));
final String cql = CQL.write(filter);
assertNotNull(cql);
- assertEquals("BBOX(att,10.0,30.0,20.0,40.0)", cql);
+ assertEquals("BBOX(att, 10.0, 30.0, 20.0, 40.0)", cql);
}
@Test
public void testBeyond() throws CQLException {
- final Filter filter = FF.beyond(FF.property("att"), FF.literal(baseGeometry), 0, "");
+ final Filter filter = FF.beyond(FF.property("att"), FF.literal(baseGeometry), Quantities.create(3, Units.METRE));
final String cql = CQL.write(filter);
assertNotNull(cql);
- assertEquals("BEYOND(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
+ assertEquals("BEYOND(att, POLYGON ((10 20, 30 40, 50 60, 10 20)), 3.0, 'meter')", cql);
}
@Test
@@ -205,7 +211,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
final Filter filter = FF.contains(FF.property("att"), FF.literal(baseGeometry));
final String cql = CQL.write(filter);
assertNotNull(cql);
- assertEquals("CONTAINS(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
+ assertEquals("CONTAINS(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
}
@Test
@@ -213,7 +219,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
final Filter filter = FF.crosses(FF.property("att"), FF.literal(baseGeometry));
final String cql = CQL.write(filter);
assertNotNull(cql);
- assertEquals("CROSSES(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
+ assertEquals("CROSSES(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
}
@Test
@@ -221,23 +227,23 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
final Filter filter = FF.disjoint(FF.property("att"), FF.literal(baseGeometry));
final String cql = CQL.write(filter);
assertNotNull(cql);
- assertEquals("DISJOINT(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
+ assertEquals("DISJOINT(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
}
@Test
public void testDWithin() throws CQLException {
- final Filter filter = FF.dwithin(FF.property("att"), FF.literal(baseGeometry), 0, "");
+ final Filter filter = FF.within(FF.property("att"), FF.literal(baseGeometry), Quantities.create(2, Units.METRE));
final String cql = CQL.write(filter);
assertNotNull(cql);
- assertEquals("DWITHIN(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
+ assertEquals("DWITHIN(att, POLYGON ((10 20, 30 40, 50 60, 10 20)), 2.0, 'meter')", cql);
}
@Test
public void testEquals() throws CQLException {
- final Filter filter = FF.equal(FF.property("att"), FF.literal(baseGeometry));
+ final Filter filter = FF.equals(FF.property("att"), FF.literal(baseGeometry));
final String cql = CQL.write(filter);
assertNotNull(cql);
- assertEquals("EQUALS(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
+ assertEquals("EQUALS(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
}
@Test
@@ -245,7 +251,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
final Filter filter = FF.intersects(FF.property("att"), FF.literal(baseGeometry));
final String cql = CQL.write(filter);
assertNotNull(cql);
- assertEquals("INTERSECTS(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
+ assertEquals("INTERSECTS(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
}
@Test
@@ -253,7 +259,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
final Filter filter = FF.overlaps(FF.property("att"), FF.literal(baseGeometry));
final String cql = CQL.write(filter);
assertNotNull(cql);
- assertEquals("OVERLAPS(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
+ assertEquals("OVERLAPS(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
}
@Test
@@ -261,7 +267,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
final Filter filter = FF.touches(FF.property("att"), FF.literal(baseGeometry));
final String cql = CQL.write(filter);
assertNotNull(cql);
- assertEquals("TOUCHES(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
+ assertEquals("TOUCHES(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
}
@Test
@@ -269,13 +275,13 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
final Filter filter = FF.within(FF.property("att"), FF.literal(baseGeometry));
final String cql = CQL.write(filter);
assertNotNull(cql);
- assertEquals("WITHIN(att,POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
+ assertEquals("WITHIN(att, POLYGON ((10 20, 30 40, 50 60, 10 20)))", cql);
}
@Ignore
@Test
public void testAfter() throws CQLException, ParseException {
- final Filter filter = FF.after(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
+ final Filter filter = FF.after(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("att AFTER 2012-03-21T05:42:36Z", cql);
@@ -284,7 +290,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Ignore
@Test
public void testAnyInteracts() throws CQLException, ParseException {
- final Filter filter = FF.anyInteracts(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
+ final Filter filter = FF.anyInteracts(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("att ANYINTERACTS 2012-03-21T05:42:36Z", cql);
@@ -293,7 +299,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Ignore
@Test
public void testBefore() throws CQLException, ParseException {
- final Filter filter = FF.before(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
+ final Filter filter = FF.before(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("att BEFORE 2012-03-21T05:42:36Z", cql);
@@ -302,7 +308,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Ignore
@Test
public void testBegins() throws CQLException, ParseException {
- final Filter filter = FF.begins(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
+ final Filter filter = FF.begins(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("att BEGINS 2012-03-21T05:42:36Z", cql);
@@ -311,7 +317,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Ignore
@Test
public void testBegunBy() throws CQLException, ParseException {
- final Filter filter = FF.begunBy(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
+ final Filter filter = FF.begunBy(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("att BEGUNBY 2012-03-21T05:42:36Z", cql);
@@ -320,7 +326,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Ignore
@Test
public void testDuring() throws CQLException, ParseException {
- final Filter filter = FF.during(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
+ final Filter filter = FF.during(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("att DURING 2012-03-21T05:42:36Z", cql);
@@ -329,7 +335,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Ignore
@Test
public void testEndedBy() throws CQLException, ParseException {
- final Filter filter = FF.endedBy(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
+ final Filter filter = FF.endedBy(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("att ENDEDBY 2012-03-21T05:42:36Z", cql);
@@ -338,7 +344,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Ignore
@Test
public void testEnds() throws CQLException, ParseException {
- final Filter filter = FF.ends(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
+ final Filter filter = FF.ends(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("att ENDS 2012-03-21T05:42:36Z", cql);
@@ -347,7 +353,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Ignore
@Test
public void testMeets() throws CQLException, ParseException {
- final Filter filter = FF.meets(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
+ final Filter filter = FF.meets(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("att MEETS 2012-03-21T05:42:36Z", cql);
@@ -356,7 +362,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Ignore
@Test
public void testMetBy() throws CQLException, ParseException {
- final Filter filter = FF.metBy(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
+ final Filter filter = FF.metBy(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("att METBY 2012-03-21T05:42:36Z", cql);
@@ -365,7 +371,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Ignore
@Test
public void testOverlappedBy() throws CQLException, ParseException {
- final Filter filter = FF.overlappedBy(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
+ final Filter filter = FF.overlappedBy(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("att OVERLAPPEDBY 2012-03-21T05:42:36Z", cql);
@@ -374,7 +380,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Ignore
@Test
public void testTcontains() throws CQLException, ParseException {
- final Filter filter = FF.tcontains(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
+ final Filter filter = FF.tcontains(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("att TCONTAINS 2012-03-21T05:42:36Z", cql);
@@ -383,7 +389,7 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Ignore
@Test
public void testTequals() throws CQLException, ParseException {
- final Filter filter = FF.tequals(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
+ final Filter filter = FF.tequals(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("att TEQUALS 2012-03-21T05:42:36Z", cql);
@@ -392,10 +398,9 @@ public final strictfp class FilterWritingTest extends CQLTestCase {
@Ignore
@Test
public void testToverlaps() throws CQLException, ParseException {
- final Filter filter = FF.toverlaps(FF.property("att"), FF.literal(parseDate("2012-03-21T05:42:36Z")));
+ final Filter filter = FF.toverlaps(FF.property("att"), FF.literal(Instant.parse("2012-03-21T05:42:36Z")));
final String cql = CQL.write(filter);
assertNotNull(cql);
assertEquals("att TOVERLAPS 2012-03-21T05:42:36Z", cql);
}
-
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java b/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
index ed7703e..95ec7ef 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/EnvelopeOperation.java
@@ -36,6 +36,7 @@ import org.apache.sis.internal.feature.Geometries;
import org.apache.sis.internal.util.CollectionsExt;
import org.apache.sis.geometry.Envelopes;
import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.internal.feature.GeometryWrapper;
import org.apache.sis.internal.feature.Resources;
import org.apache.sis.referencing.CRS;
import org.apache.sis.util.resources.Errors;
@@ -320,7 +321,8 @@ final class EnvelopeOperation extends AbstractOperation {
* we will do two calls to some `Feature.getProperty…` method, which results in two lookups
* in hash table. We presume that the gain from the optimistic assumption is worth the cost.
*/
- GeneralEnvelope genv = Geometries.getEnvelope(feature.getPropertyValue(attributeNames[i])).orElse(null);
+ GeneralEnvelope genv = Geometries.wrap(feature.getPropertyValue(attributeNames[i]))
+ .map(GeometryWrapper::getEnvelope).orElse(null);
if (genv == null) {
continue;
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
index cfa93d5..da555c0 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/feature/FeatureFormat.java
@@ -46,6 +46,7 @@ import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.internal.util.Strings;
import org.apache.sis.internal.util.CollectionsExt;
import org.apache.sis.internal.feature.Geometries;
+import org.apache.sis.internal.feature.GeometryWrapper;
import org.apache.sis.internal.system.Modules;
import org.apache.sis.referencing.IdentifiedObjects;
import org.apache.sis.math.MathFunctions;
@@ -723,7 +724,7 @@ format: for (final AttributeType<?> ct : ((AttributeType<?>)
} else if (value instanceof IdentifiedObject) {
text = IdentifiedObjects.getIdentifierOrName((IdentifiedObject) value);
} else {
- text = Geometries.toString(value).orElseGet(value::toString);
+ text = Geometries.wrap(value).map(GeometryWrapper::toString).orElseGet(value::toString);
}
final int remaining = MAXIMAL_VALUE_LENGTH - length;
if (remaining >= text.length()) {
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
index 9a08fb3..9f9f6dd 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
@@ -18,20 +18,19 @@ package org.apache.sis.filter;
import java.math.BigDecimal;
import java.math.BigInteger;
-import org.apache.sis.util.Numbers;
-import org.apache.sis.util.ArgumentChecks;
+import org.opengis.util.ScopedName;
import org.apache.sis.feature.builder.FeatureTypeBuilder;
import org.apache.sis.feature.builder.PropertyTypeBuilder;
import org.apache.sis.internal.feature.FeatureExpression;
-import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.internal.filter.FunctionNames;
+import org.apache.sis.util.UnconvertibleObjectException;
+import org.apache.sis.util.resources.Errors;
import org.apache.sis.math.Fraction;
// Branch-dependent imports
import org.opengis.feature.AttributeType;
import org.opengis.feature.FeatureType;
-import org.opengis.filter.expression.BinaryExpression;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.ExpressionVisitor;
+import org.opengis.filter.Expression;
/**
@@ -41,10 +40,15 @@ import org.opengis.filter.expression.ExpressionVisitor;
* @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}) used as inputs.
+ *
+ * @since 1.1
* @module
*/
-abstract class ArithmeticFunction extends BinaryFunction implements BinaryExpression, FeatureExpression {
+abstract class ArithmeticFunction<R> extends BinaryFunction<R,Number,Number>
+ implements FeatureExpression<R,Number>, Optimization.OnExpression<R,Number>
+{
/**
* For cross-version compatibility.
*/
@@ -53,7 +57,9 @@ abstract class ArithmeticFunction extends BinaryFunction implements BinaryExpres
/**
* Creates a new arithmetic function.
*/
- ArithmeticFunction(final Expression expression1, final Expression expression2) {
+ ArithmeticFunction(final Expression<? super R, ? extends Number> expression1,
+ final Expression<? super R, ? extends Number> expression2)
+ {
super(expression1, expression2);
}
@@ -74,6 +80,14 @@ abstract class ArithmeticFunction extends BinaryFunction implements BinaryExpres
protected abstract AttributeType<Number> expectedType();
/**
+ * Returns the type of values computed by this expression.
+ */
+ @Override
+ public final Class<?> getValueClass() {
+ return Number.class;
+ }
+
+ /**
* Provides the type of results computed by this expression. That type depends only
* on the {@code ArithmeticFunction} subclass and is given by {@link #expectedType()}.
*/
@@ -83,70 +97,69 @@ abstract class ArithmeticFunction extends BinaryFunction implements BinaryExpres
}
/**
- * Evaluates this expression based on the content of the given object. This method delegates to
- * {@link #applyAsDouble(double, double)}, {@link #applyAsLong(long, long)} or similar methods
- * depending on the value types.
- *
- * @throws ArithmeticException if the operation overflows the capacity of the type used.
+ * Evaluates the expression for producing a result of numeric type.
+ * This method delegates to one of the {@code applyAs(…)} methods.
+ * If no {@code applyAs(…)} implementations can return null values,
+ * this this method never return {@code null}.
*/
@Override
- public final Object evaluate(final Object feature) {
- return evaluate(feature, Number.class);
+ public final Number apply(final R feature) {
+ final Number left = expression1.apply(feature);
+ if (left != null) {
+ final Number right = expression2.apply(feature);
+ if (right != null) {
+ return apply(left, right);
+ }
+ }
+ return null;
}
/**
- * Evaluates the expression for producing a result of the given type. This method delegates to
- * {@link #applyAsDouble(double, double)}, {@link #applyAsLong(long, long)} or similar methods
- * depending on the value types. If this method can not produce a value of the given type,
- * then it returns {@code null}.
+ * Returns {@code this} if this expression provides values of the specified type,
+ * or otherwise returns an expression doing conversions on-the-fly.
*
- * @param feature to feature to evaluate with this expression.
- * @param target the desired type for the expression result.
- * @return the result, or {@code null} if it can not be of the specified type.
- * @throws ClassCastException if an expression returned the value in an expected type.
- * @throws ArithmeticException if the operation overflows the capacity of the type used.
+ * @throws ClassCastException if the specified type is not a supported target type.
*/
@Override
@SuppressWarnings("unchecked")
- public final <T> T evaluate(final Object feature, final Class<T> target) {
- ArgumentChecks.ensureNonNull("target", target);
- if (Number.class.isAssignableFrom(target)) try {
- final Number left = (Number) expression1.evaluate(feature, target);
- if (left != null) {
- final Number right = (Number) expression2.evaluate(feature, target);
- if (right != null) {
- final Number result = apply(left, right);
- final Number casted = Numbers.cast(result, (Class<? extends Number>) target);
- if (Numerics.equals(result.doubleValue(), casted.doubleValue())) {
- return (T) casted;
- }
- }
- }
- } catch (ArithmeticException e) {
- warning(e);
+ public <N> Expression<R,N> toValueType(final Class<N> type) {
+ if (type.isAssignableFrom(Number.class)) {
+ return (Expression<R,N>) this;
+ } else try {
+ return new ConvertFunction<>(this, Number.class, type);
+ } catch (UnconvertibleObjectException e) {
+ throw (ClassCastException) new ClassCastException(Errors.format(
+ Errors.Keys.CanNotConvertValue_2, getFunctionName(), type)).initCause(e);
}
- return null;
}
-
/**
* The "Add" (+) expression.
*/
- static final class Add extends ArithmeticFunction implements org.opengis.filter.expression.Add {
+ static final class Add<R> extends ArithmeticFunction<R> {
/** For cross-version compatibility during (de)serialization. */
private static final long serialVersionUID = 5445433312445869201L;
- /** Description of results of the {@value #NAME} expression. */
- private static final AttributeType<Number> TYPE = createNumericType(NAME);
+ /** Description of results of the {@code "Add"} expression. */
+ private static final AttributeType<Number> TYPE = createNumericType(FunctionNames.Add);
@Override protected AttributeType<Number> expectedType() {return TYPE;}
- /** Creates a new expression for the {@value #NAME} operation. */
- Add(Expression expression1, Expression expression2) {
+ /** Creates a new expression for the {@code "Add"} operation. */
+ Add(final Expression<? super R, ? extends Number> expression1,
+ final Expression<? super R, ? extends Number> expression2)
+ {
super(expression1, expression2);
}
- /** Identification of this operation. */
- @Override public String getName() {return NAME;}
+ /** Creates a new expression of the same type but different parameters. */
+ @Override public Expression<R,Number> recreate(final Expression<? super R, ?>[] effective) {
+ return new Add<>(effective[0].toValueType(Number.class),
+ effective[1].toValueType(Number.class));
+ }
+
+ /** Identification of the {@code "Add"} operation. */
+ private static final ScopedName NAME = createName(FunctionNames.Add);
+ @Override public ScopedName getFunctionName() {return NAME;}
@Override protected char symbol() {return '+';}
/** Applies this expression to the given operands. */
@@ -155,32 +168,36 @@ abstract class ArithmeticFunction extends BinaryFunction implements BinaryExpres
@Override protected Number applyAsDecimal (BigDecimal left, BigDecimal right) {return left.add(right);}
@Override protected Number applyAsInteger (BigInteger left, BigInteger right) {return left.add(right);}
@Override protected Number applyAsLong (long left, long right) {return Math.addExact(left, right);}
-
- /** Implementation of the visitor pattern (not used by Apache SIS). */
- @Override public Object accept(ExpressionVisitor visitor, Object extraData) {
- return visitor.visit(this, extraData);
- }
}
/**
- * The "Sub" (−) expression.
+ * The "Subtract" (−) expression.
*/
- static final class Subtract extends ArithmeticFunction implements org.opengis.filter.expression.Subtract {
+ static final class Subtract<R> extends ArithmeticFunction<R> {
/** For cross-version compatibility during (de)serialization. */
private static final long serialVersionUID = 3048878022726271508L;
- /** Description of results of the {@value #NAME} expression. */
- private static final AttributeType<Number> TYPE = createNumericType(NAME);
+ /** Description of results of the {@code "Subtract"} expression. */
+ private static final AttributeType<Number> TYPE = createNumericType(FunctionNames.Subtract);
@Override protected AttributeType<Number> expectedType() {return TYPE;}
- /** Creates a new expression for the {@value #NAME} operation. */
- Subtract(Expression expression1, Expression expression2) {
+ /** Creates a new expression for the {@code "Subtract"} operation. */
+ Subtract(final Expression<? super R, ? extends Number> expression1,
+ final Expression<? super R, ? extends Number> expression2)
+ {
super(expression1, expression2);
}
- /** Identification of this operation. */
- @Override public String getName() {return NAME;}
+ /** Creates a new expression of the same type but different parameters. */
+ @Override public Expression<R,Number> recreate(final Expression<? super R, ?>[] effective) {
+ return new Subtract<>(effective[0].toValueType(Number.class),
+ effective[1].toValueType(Number.class));
+ }
+
+ /** Identification of the {@code "Subtract"} operation. */
+ private static final ScopedName NAME = createName(FunctionNames.Subtract);
+ @Override public ScopedName getFunctionName() {return NAME;}
@Override protected char symbol() {return '−';}
/** Applies this expression to the given operands. */
@@ -189,32 +206,36 @@ abstract class ArithmeticFunction extends BinaryFunction implements BinaryExpres
@Override protected Number applyAsDecimal (BigDecimal left, BigDecimal right) {return left.subtract(right);}
@Override protected Number applyAsInteger (BigInteger left, BigInteger right) {return left.subtract(right);}
@Override protected Number applyAsLong (long left, long right) {return Math.subtractExact(left, right);}
-
- /** Implementation of the visitor pattern (not used by Apache SIS). */
- @Override public Object accept(ExpressionVisitor visitor, Object extraData) {
- return visitor.visit(this, extraData);
- }
}
/**
- * The "Mul" (×) expression.
+ * The "Multiply" (×) expression.
*/
- static final class Multiply extends ArithmeticFunction implements org.opengis.filter.expression.Multiply {
+ static final class Multiply<R> extends ArithmeticFunction<R> {
/** For cross-version compatibility during (de)serialization. */
private static final long serialVersionUID = -1300022614832645625L;
- /** Description of results of the {@value #NAME} expression. */
- private static final AttributeType<Number> TYPE = createNumericType(NAME);
+ /** Description of results of the {@code "Multiply"} expression. */
+ private static final AttributeType<Number> TYPE = createNumericType(FunctionNames.Multiply);
@Override protected AttributeType<Number> expectedType() {return TYPE;}
- /** Creates a new expression for the {@value #NAME} operation. */
- Multiply(Expression expression1, Expression expression2) {
+ /** Creates a new expression for the {@code "Multiply"} operation. */
+ Multiply(final Expression<? super R, ? extends Number> expression1,
+ final Expression<? super R, ? extends Number> expression2)
+ {
super(expression1, expression2);
}
- /** Identification of this operation. */
- @Override public String getName() {return NAME;}
+ /** Creates a new expression of the same type but different parameters. */
+ @Override public Expression<R,Number> recreate(final Expression<? super R, ?>[] effective) {
+ return new Multiply<>(effective[0].toValueType(Number.class),
+ effective[1].toValueType(Number.class));
+ }
+
+ /** Identification of the {@code "Multiply"} operation. */
+ private static final ScopedName NAME = createName(FunctionNames.Multiply);
+ @Override public ScopedName getFunctionName() {return NAME;}
@Override protected char symbol() {return '×';}
/** Applies this expression to the given operands. */
@@ -223,35 +244,39 @@ abstract class ArithmeticFunction extends BinaryFunction implements BinaryExpres
@Override protected Number applyAsDecimal (BigDecimal left, BigDecimal right) {return left.multiply(right);}
@Override protected Number applyAsInteger (BigInteger left, BigInteger right) {return left.multiply(right);}
@Override protected Number applyAsLong (long left, long right) {return Math.multiplyExact(left, right);}
-
- /** Implementation of the visitor pattern (not used by Apache SIS). */
- @Override public Object accept(ExpressionVisitor visitor, Object extraData) {
- return visitor.visit(this, extraData);
- }
}
/**
- * The "Div" (÷) expression.
+ * The "Divide" (÷) expression.
*/
- static final class Divide extends ArithmeticFunction implements org.opengis.filter.expression.Divide {
+ static final class Divide<R> extends ArithmeticFunction<R> {
/** For cross-version compatibility during (de)serialization. */
private static final long serialVersionUID = -7709291845568648891L;
- /** Description of results of the {@value #NAME} expression. */
- private static final AttributeType<Number> TYPE = createNumericType(NAME);
+ /** Description of results of the {@code "Divide"} expression. */
+ private static final AttributeType<Number> TYPE = createNumericType(FunctionNames.Divide);
@Override protected AttributeType<Number> expectedType() {return TYPE;}
- /** Creates a new expression for the {@value #NAME} operation. */
- Divide(Expression expression1, Expression expression2) {
+ /** Creates a new expression for the {@code "Divide"} operation. */
+ Divide(final Expression<? super R, ? extends Number> expression1,
+ final Expression<? super R, ? extends Number> expression2)
+ {
super(expression1, expression2);
}
- /** Identification of this operation. */
- @Override public String getName() {return NAME;}
+ /** Creates a new expression of the same type but different parameters. */
+ @Override public Expression<R,Number> recreate(final Expression<? super R, ?>[] effective) {
+ return new Divide<>(effective[0].toValueType(Number.class),
+ effective[1].toValueType(Number.class));
+ }
+
+ /** Identification of the {@code "Divide"} operation. */
+ private static final ScopedName NAME = createName(FunctionNames.Divide);
+ @Override public ScopedName getFunctionName() {return NAME;}
@Override protected char symbol() {return '÷';}
- /** Applies this expression to the given operands. */
+ /** Divides the given integers, changing the type if the result is not an integer. */
@Override protected Number applyAsDouble (double left, double right) {return left / right;}
@Override protected Number applyAsFraction(Fraction left, Fraction right) {return left.divide(right);}
@Override protected Number applyAsDecimal (BigDecimal left, BigDecimal right) {return left.divide(right);}
@@ -260,22 +285,19 @@ abstract class ArithmeticFunction extends BinaryFunction implements BinaryExpres
if (BigInteger.ZERO.equals(r[1])) {
return r[0];
} else {
- return left.doubleValue() / right.doubleValue();
+ return new Fraction(r[1].intValueExact(), right.intValueExact()).add(
+ new Fraction(r[0].intValueExact(), 1));
}
}
- /** Divides the given integers, changing the type to a floating point type if the result is not an integer. */
+ /** Divides the given integers, changing the type if the result is not an integer. */
@Override protected Number applyAsLong(final long left, final long right) {
+ final long r = left / right;
if (left % right == 0) {
- return left / right;
+ return r;
} else {
- return left / (double) right;
+ return new Fraction(Math.toIntExact(left), Math.toIntExact(right));
}
}
-
- /** Implementation of the visitor pattern (not used by Apache SIS). */
- @Override public Object accept(ExpressionVisitor visitor, Object extraData) {
- return visitor.visit(this, extraData);
- }
}
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
index 19aab05..ad9064d 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
@@ -16,6 +16,7 @@
*/
package org.apache.sis.filter;
+import java.util.List;
import java.util.Arrays;
import java.util.Collection;
import java.math.BigInteger;
@@ -27,7 +28,8 @@ import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.internal.filter.Node;
// Branch-dependent imports
-import org.opengis.filter.expression.Expression;
+import org.opengis.filter.Filter;
+import org.opengis.filter.Expression;
/**
@@ -39,10 +41,15 @@ import org.opengis.filter.expression.Expression;
* @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}) used as inputs.
+ * @param <V1> the type of value computed by the first expression.
+ * @param <V2> the type of value computed by the second expression.
+ *
+ * @since 1.1
* @module
*/
-abstract class BinaryFunction extends Node {
+abstract class BinaryFunction<R,V1,V2> extends Node {
/**
* For cross-version compatibility.
*/
@@ -53,14 +60,14 @@ abstract class BinaryFunction extends Node {
*
* @see #getExpression1()
*/
- protected final Expression expression1;
+ protected final Expression<? super R, ? extends V1> expression1;
/**
* The second of the two expressions to be used by this function.
*
* @see #getExpression2()
*/
- protected final Expression expression2;
+ protected final Expression<? super R, ? extends V2> expression2;
/**
* Creates a new binary function.
@@ -68,7 +75,9 @@ abstract class BinaryFunction extends Node {
* @param expression1 the first of the two expressions to be used by this function.
* @param expression2 the second of the two expressions to be used by this function.
*/
- protected BinaryFunction(final Expression expression1, final Expression expression2) {
+ protected BinaryFunction(final Expression<? super R, ? extends V1> expression1,
+ final Expression<? super R, ? extends V2> expression2)
+ {
ArgumentChecks.ensureNonNull("expression1", expression1);
ArgumentChecks.ensureNonNull("expression2", expression2);
this.expression1 = expression1;
@@ -76,34 +85,30 @@ abstract class BinaryFunction extends Node {
}
/**
- * Returns the first of the two expressions to be used by this function.
- * This is the value specified at construction time.
- *
- * @see org.opengis.filter.BinaryComparisonOperator#getExpression1()
- * @see org.opengis.filter.temporal.BinaryTemporalOperator#getExpression1()
+ * Returns the expressions used as parameters by this function.
+ * Defined for {@link Expression#getParameters()} implementations.
*/
- public final Expression getExpression1() {
- return expression1;
+ public final List<Expression<? super R, ?>> getParameters() {
+ return getExpressions();
}
/**
- * Returns the second of the two expressions to be used by this function.
- * This is the value specified at construction time.
+ * Returns the two expressions used as parameters by this filter.
+ * Defined for {@link Filter#getExpressions()} implementations.
*
- * @see org.opengis.filter.BinaryComparisonOperator#getExpression1()
- * @see org.opengis.filter.temporal.BinaryTemporalOperator#getExpression2()
+ * @return a list of size 2 containing the two expressions.
*/
- public final Expression getExpression2() {
- return expression2;
+ public List<Expression<? super R, ?>> getExpressions() {
+ return Arrays.asList(expression1, expression2); // TODO: use List.of(…) with JDK9.
}
/**
* Returns the two expressions in a list of size 2.
- * This is used for {@link #toString()} implementation.
+ * This is used for {@link #toString()}, {@link #hashCode()} and {@link #equals(Object)} implementations.
*/
@Override
protected final Collection<?> getChildren() {
- return Arrays.asList(expression1, expression2);
+ return getExpressions();
}
/**
@@ -116,98 +121,97 @@ abstract class BinaryFunction extends Node {
* @param right the right operand. Can not be null.
* @return result of this function applied on the two given operands.
* May be {@code null} only if an {@code applyAs(…)} implementation returned a null value.
- * @throws ArithmeticException if the operation overflows the capacity of the type used.
*/
protected final Number apply(final Number left, final Number right) {
- switch (Math.max(Numbers.getEnumConstant(left.getClass()),
- Numbers.getEnumConstant(right.getClass())))
- {
- case Numbers.BIG_DECIMAL: {
- return applyAsDecimal(Numbers.cast(left, BigDecimal.class),
- Numbers.cast(right, BigDecimal.class));
- }
- case Numbers.BIG_INTEGER: {
- return applyAsInteger(Numbers.cast(left, BigInteger.class),
- Numbers.cast(right, BigInteger.class));
- }
- case Numbers.FRACTION: {
- return applyAsFraction(Numbers.cast(left, Fraction.class),
- Numbers.cast(right, Fraction.class));
- }
- case Numbers.LONG:
- case Numbers.INTEGER:
- case Numbers.SHORT:
- case Numbers.BYTE: {
- return applyAsLong(left.longValue(), right.longValue());
+ final int type = Math.max(Numbers.getEnumConstant(left.getClass()),
+ Numbers.getEnumConstant(right.getClass()));
+ try {
+ switch (type) {
+ case Numbers.BIG_DECIMAL: {
+ return applyAsDecimal(Numbers.cast(left, BigDecimal.class),
+ Numbers.cast(right, BigDecimal.class));
+ }
+ case Numbers.BIG_INTEGER: {
+ return applyAsInteger(Numbers.cast(left, BigInteger.class),
+ Numbers.cast(right, BigInteger.class));
+ }
+ case Numbers.FRACTION: {
+ return applyAsFraction(Numbers.cast(left, Fraction.class),
+ Numbers.cast(right, Fraction.class));
+ }
+ case Numbers.LONG:
+ case Numbers.INTEGER:
+ case Numbers.SHORT:
+ case Numbers.BYTE: {
+ return applyAsLong(left.longValue(), right.longValue());
+ }
}
+ } catch (IllegalArgumentException | ArithmeticException e) {
+ /*
+ * Integer overflow, or division by zero, or attempt to convert NaN or infinity
+ * to `BigDecimal`, or division does not have a terminating decimal expansion.
+ */
+ warning(e, true);
}
return applyAsDouble((left instanceof Float) ? DecimalFunctions.floatToDouble((Float) left) : left.doubleValue(),
(right instanceof Float) ? DecimalFunctions.floatToDouble((Float) right) : right.doubleValue());
}
/**
- * Calculates this function using given operands of {@code long} primitive type. If this function is a
- * filter, then this method should returns an {@link Integer} value 0 or 1 for false or true respectively.
- * Otherwise the result is usually a {@link Long}, except for division which may produce a floating point number.
+ * Calculates this function using given operands of {@code long} primitive type. If this function is a filter,
+ * then this method should returns an {@link Integer} value 0 or 1 for false or true respectively.
+ * Otherwise the result is usually a {@link Long}, except for division which may produce other types.
* This method may return {@code null} if the operation can not apply on numbers.
*
- * @throws ArithmeticException if the operation overflows the 64 bits integer capacity.
+ * @throws ArithmeticException if the operation overflows or if there is a division by zero.
*/
- protected abstract Number applyAsLong(long left, long right);
+ protected Number applyAsLong(long left, long right) {
+ return null;
+ }
/**
- * Calculates this function using given operands of {@code double} primitive type. If this function is a
- * filter, then this method should returns an {@link Integer} value 0 or 1 for false or true respectively.
+ * Calculates this function using given operands of {@code double} primitive type. If this function is a filter,
+ * then this method should returns an {@link Integer} value 0 or 1 for false or true respectively.
* Otherwise the result is usually a {@link Double}.
* This method may return {@code null} if the operation can not apply on numbers.
*/
- protected abstract Number applyAsDouble(double left, double right);
+ protected Number applyAsDouble(double left, double right) {
+ return null;
+ }
/**
* Calculates this function using given operands of {@code Fraction} type. If this function is a filter,
* then this method should returns an {@link Integer} value 0 or 1 for false or true respectively.
* Otherwise the result is usually a {@link Fraction}.
* This method may return {@code null} if the operation can not apply on numbers.
+ *
+ * @throws ArithmeticException if the operation overflows or if there is a division by zero.
*/
- protected abstract Number applyAsFraction(Fraction left, Fraction right);
+ protected Number applyAsFraction(Fraction left, Fraction right) {
+ return null;
+ }
/**
* Calculates this function using given operands of {@code BigInteger} type. If this function is a filter,
* then this method should returns an {@link Integer} value 0 or 1 for false or true respectively.
- * Otherwise the result is usually a {@link BigInteger}.
+ * Otherwise the result is usually a {@link BigInteger}, except for division which may produce other types.
* This method may return {@code null} if the operation can not apply on numbers.
+ *
+ * @throws ArithmeticException if there is a division by zero.
*/
- protected abstract Number applyAsInteger(BigInteger left, BigInteger right);
+ protected Number applyAsInteger(BigInteger left, BigInteger right) {
+ return null;
+ }
/**
* Calculates this function using given operands of {@code BigDecimal} type. If this function is a filter,
* then this method should returns an {@link Integer} value 0 or 1 for false or true respectively.
* Otherwise the result is usually a {@link BigDecimal}.
* This method may return {@code null} if the operation can not apply on numbers.
+ *
+ * @throws ArithmeticException if a division does not have a terminating decimal expansion.
*/
- protected abstract Number applyAsDecimal(BigDecimal left, BigDecimal right);
-
- /**
- * Returns a hash code value for this function.
- */
- @Override
- public int hashCode() {
- return (31 * expression1.hashCode() + expression2.hashCode()) ^ getClass().hashCode();
- }
-
- /**
- * Compares this function with the given object for equality.
- */
- @Override
- public boolean equals(final Object obj) {
- if (obj == this) {
- return true;
- }
- if (obj != null && obj.getClass() == getClass()) {
- final BinaryFunction other = (BinaryFunction) obj;
- return expression1.equals(other.expression1) &&
- expression2.equals(other.expression2);
- }
- return false;
+ protected Number applyAsDecimal(BigDecimal left, BigDecimal right) {
+ return null;
}
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryGeometryFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryGeometryFilter.java
new file mode 100644
index 0000000..477e59e
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryGeometryFilter.java
@@ -0,0 +1,212 @@
+/*
+ * 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 javax.measure.Unit;
+import javax.measure.IncommensurableException;
+import org.opengis.util.FactoryException;
+import org.opengis.referencing.operation.TransformException;
+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.internal.filter.Node;
+import org.apache.sis.util.ArgumentChecks;
+
+// Branch-dependent imports
+import org.opengis.filter.Filter;
+import org.opengis.filter.Literal;
+import org.opengis.filter.Expression;
+import org.opengis.filter.SpatialOperator;
+import org.opengis.filter.InvalidFilterValueException;
+
+
+/**
+ * Base class for filters having two expressions evaluating to geometries.
+ * In addition of 2 geometries, the filter can have additional non-geometric arguments.
+ * The nature of the operation depends on the subclass.
+ *
+ * @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.
+ * @param <G> the implementation type of geometry objects.
+ *
+ * @since 1.1
+ * @module
+ */
+abstract class BinaryGeometryFilter<R,G> extends Node implements SpatialOperator<R>, Optimization.OnFilter<R> {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = -7205680763469213064L;
+
+ /**
+ * The first of the two expressions to be used by this function.
+ *
+ * @see #getExpression1()
+ */
+ protected final Expression<? super R, GeometryWrapper<G>> expression1;
+
+ /**
+ * The second of the two expressions to be used by this function.
+ *
+ * @see #getExpression2()
+ */
+ protected final Expression<? super R, GeometryWrapper<G>> expression2;
+
+ /**
+ * The preferred CRS and other context to use if geometry transformations are needed.
+ */
+ protected final SpatialOperationContext context;
+
+ /**
+ * Creates a new binary function.
+ *
+ * @param geometry1 the first of the two expressions to be used by this function.
+ * @param geometry2 the second of the two expressions to be used by this function.
+ * @param systemUnit if the CRS needs to be in some units of measurement, the {@link Unit#getSystemUnit()} value.
+ */
+ protected BinaryGeometryFilter(final Geometries<G> library,
+ final Expression<? super R, ?> geometry1,
+ final Expression<? super R, ?> geometry2,
+ final Unit<?> systemUnit)
+ {
+ ArgumentChecks.ensureNonNull("expression1", geometry1);
+ ArgumentChecks.ensureNonNull("expression2", geometry2);
+ Expression<? super R, GeometryWrapper<G>> expression1, expression2;
+ expression1 = toGeometryWrapper(library, geometry1);
+ expression2 = toGeometryWrapper(library, geometry2);
+ /*
+ * Check if any expression is a literal. If an expression is a literal, we use
+ * its Coordinate Reference System as the CRS in which to perform the operation.
+ * Otherwise the CRS will be selected on a case-by-case basis at evaluation time.
+ */
+ final int index;
+ final Literal<? super R, ?> literal;
+ if (geometry2 instanceof Literal<?,?>) {
+ literal = (Literal<? super R, ?>) geometry2;
+ index = 1;
+ } else if (geometry1 instanceof Literal<?,?>) {
+ literal = (Literal<? super R, ?>) geometry1;
+ index = 0;
+ } else {
+ literal = null;
+ index = -1;
+ }
+ final GeometryWrapper<G> value = (literal == null) ? null : library.castOrWrap(literal.getValue());
+ try {
+ context = new SpatialOperationContext(null, value, systemUnit, index);
+ if (value != null) {
+ final GeometryWrapper<G> gt = context.transform(value);
+ if (gt != value) {
+ final Expression<? super R, GeometryWrapper<G>> tr = new LeafExpression.Transformed<>(gt, literal);
+ switch (index) {
+ case 0: expression1 = tr; break;
+ case 1: expression2 = tr; break;
+ default: throw new AssertionError(index);
+ }
+ }
+ }
+ } catch (FactoryException | TransformException | IncommensurableException e) {
+ throw new InvalidFilterValueException(e);
+ }
+ this.expression1 = expression1;
+ this.expression2 = expression2;
+ }
+
+ /**
+ * Recreates a new filter of the same type and with the same parameters, but using the given expressions.
+ * This method is invoked when it is possible to simplify or optimize at least one of the expressions that
+ * were given in the original call to the constructor.
+ */
+ protected abstract BinaryGeometryFilter<R,G> recreate(final Expression<? super R, ?> geometry1,
+ final Expression<? super R, ?> geometry2);
+
+ /**
+ * Returns the original expression specified by the user.
+ *
+ * @param <R> the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param <G> the geometry implementation type.
+ * @param expression the expression to unwrap.
+ * @return the unwrapped expression.
+ */
+ protected static <R,G> Expression<? super R, ?> original(final Expression<R, ? extends GeometryWrapper<G>> expression) {
+ if (expression instanceof LeafExpression.Transformed<?,?>) {
+ return ((LeafExpression.Transformed<R,?>) expression).original;
+ } else {
+ return unwrap(expression);
+ }
+ }
+
+ /**
+ * Returns the two expressions used as parameters by this filter.
+ */
+ @Override
+ public List<Expression<? super R, ?>> getExpressions() {
+ return Arrays.asList(original(expression1), original(expression2)); // TODO: use List.of(…) with JDK9.
+ }
+
+ /**
+ * Tries to optimize this filter. This method checks if any expression is a literal.
+ * If both expressions are literal, we can evaluate immediately. If any expression
+ * is a literal and returns {@code null}, then the result is known in advance too.
+ */
+ @Override
+ public final Filter<? super R> optimize(final Optimization optimization) {
+ final Expression<? super R, ?> geometry1 = unwrap(expression1);
+ final Expression<? super R, ?> geometry2 = unwrap(expression2);
+ final Expression<? super R, ?> effective1 = optimization.apply(geometry1);
+ final Expression<? super R, ?> effective2 = optimization.apply(geometry2);
+ final Literal<? super R, ?> literal;
+ final boolean immediate; // true if the filter should be evaluated immediately.
+ final boolean literalIsNull; // true if one of the literal value is null.
+ if (effective2 instanceof Literal<?,?>) {
+ literal = (Literal<? super R, ?>) effective2;
+ immediate = (effective1 instanceof Literal<?,?>);
+ } else if (effective1 instanceof Literal<?,?>) {
+ literal = (Literal<? super R, ?>) effective1;
+ immediate = false;
+ } else {
+ return this;
+ }
+ literalIsNull = (literal.getValue() == null);
+ final boolean result;
+ if (literalIsNull) {
+ // If the literal has no value, then the filter will always evaluate to a negative result.
+ result = negativeResult();
+ } else {
+ Filter<? super R> filter = this;
+ if ((effective1 != geometry1) || (effective2 != geometry2)) {
+ filter = recreate(effective1, effective2);
+ }
+ if (!immediate) {
+ return filter;
+ }
+ // If all operands are literal, we can evaluate the expression immediately.
+ result = filter.test(null);
+ }
+ return result ? Filter.include() : Filter.exclude();
+ }
+
+ /**
+ * Returns the value to return when a test can not be applied.
+ */
+ protected abstract boolean negativeResult();
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/CRSMatching.java b/core/sis-feature/src/main/java/org/apache/sis/filter/CRSMatching.java
deleted file mode 100644
index c85b512..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/CRSMatching.java
+++ /dev/null
@@ -1,167 +0,0 @@
-package org.apache.sis.filter;
-
-import java.util.Optional;
-
-import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.operation.CoordinateOperation;
-import org.opengis.referencing.operation.TransformException;
-import org.opengis.util.FactoryException;
-
-import org.apache.sis.referencing.CRS;
-import org.apache.sis.util.Utilities;
-import org.apache.sis.util.collection.BackingStoreException;
-
-import static org.apache.sis.referencing.IdentifiedObjects.getIdentifierOrName;
-
-/**
- * Tries to find a common space for two given CRS operands. The common space is defined using {@link CRS#suggestCommonTarget(GeographicBoundingBox, CoordinateReferenceSystem...)}.
- * Usage: initialize through {@link #left(CoordinateReferenceSystem)}, giving your first (left) operand as argument.
- * Then use {@link #right(CoordinateReferenceSystem)} to specify the other (right) operand.
- *
- * TODO: improve CRS conversion/suggestion by allowing user to input a geographic region of interest.
- */
-@FunctionalInterface
-public interface CRSMatching {
-
- static CRSMatching left(final CoordinateReferenceSystem source) {
- if (source == null) {
- return other -> {
- if (other == null) return new NullMatch();
- else throw new IllegalArgumentException("No match can be established with given CRS because source one is null.");
- };
- } else {
- return other -> {
- if (other == null) throw new IllegalArgumentException("No match can be established with previous CRS because input one is null");
- final CoordinateReferenceSystem commonCrs = CRS.suggestCommonTarget(null, source, other);
- if (commonCrs == null) throw new IllegalArgumentException(String.format(
- "No common space can be found between %s and %s",
- getIdentifierOrName(source), getIdentifierOrName(other)
- ));
- return new DefaultMatch(commonCrs, source, other);
- };
- }
- }
-
- Match right(final CoordinateReferenceSystem rightCrs);
-
- /**
- * Defines that a common space has been found for both input operands.
- */
- abstract class Match {
-
- /**
- *
- * @return If both input CRS were null, an empty shell. Otherwise, the common space defined by {@link CRS#suggestCommonTarget(GeographicBoundingBox, CoordinateReferenceSystem...)}.
- * Note that it can be one of the original systems (in which cas one of the operations provided will be empty),
- * or a third-party one (common geographic base, for example).
- */
- abstract Optional<CoordinateReferenceSystem> getCommonCRS();
-
- /**
- *
- * @return Coordinate operation to use for going from left operand system to common space. Can be empty if the
- * common space is equal to left operand system.
- */
- abstract Optional<CoordinateOperation> fromLeft();
-
- /**
- *
- * @return Coordinate operation to use for going from right operand system to common space. Can be empty if the
- * common space is equal to right operand system.
- */
- abstract Optional<CoordinateOperation> fromRight();
-
- public final <L> L transformLeftToCommon(L leftValue, Transformer<L> operator) throws TransformException {
- return transform(leftValue, fromLeft(), operator);
- }
-
- public final <R> R transformRightToCommon(R rightValue, Transformer<R> operator) throws TransformException {
- return transform(rightValue, fromRight(), operator);
- }
- }
-
- final class NullMatch extends Match {
-
- @Override
- public Optional<CoordinateReferenceSystem> getCommonCRS() {
- return Optional.empty();
- }
-
- @Override
- public Optional<CoordinateOperation> fromLeft() {
- return Optional.empty();
- }
-
- @Override
- public Optional<CoordinateOperation> fromRight() {
- return Optional.empty();
- }
- }
-
- final class DefaultMatch extends Match {
-
- final CoordinateReferenceSystem commonCRS;
- final Optional<CoordinateOperation> fromLeft;
- final Optional<CoordinateOperation> fromRight;
-
- public DefaultMatch(CoordinateReferenceSystem commonCRS, CoordinateReferenceSystem leftCrs, CoordinateReferenceSystem rightCrs) {
- this.commonCRS = commonCRS;
- try {
- fromLeft = createOp(leftCrs, commonCRS);
- fromRight = createOp(rightCrs, commonCRS);
- } catch (FactoryException e) {
- throw new BackingStoreException(e);
- }
- }
-
- @Override
- public Optional<CoordinateReferenceSystem> getCommonCRS() {
- return Optional.of(commonCRS);
- }
-
- @Override
- public Optional<CoordinateOperation> fromLeft() {
- return fromLeft;
- }
-
- @Override
- public Optional<CoordinateOperation> fromRight() {
- return fromRight;
- }
- }
-
- static Optional<CoordinateOperation> createOp(final CoordinateReferenceSystem source, final CoordinateReferenceSystem target) throws FactoryException {
- if (Utilities.equalsIgnoreMetadata(source, target)) {
- return Optional.empty();
- } else {
- final CoordinateOperation op = CRS.findOperation(source, target, null);
- return Optional.of(op);
- }
- }
-
- /**
- * TODO: We should NOT accept factory exception here. The transform uses an already built coordinate operation, no
- * CRS factory should be needed in the process.
- * @param <T> Type of object to transform (envelope, geometry, etc.).
- */
- @FunctionalInterface
- interface Transformer<T> {
- T apply(T operand, CoordinateOperation op) throws TransformException, FactoryException;
- }
-
- static <T> T transform(T in, Optional<CoordinateOperation> coordOp, Transformer<T> transformer) throws TransformException {
- try {
- return coordOp.map(op -> {
- try {
- return transformer.apply(in, op);
- } catch (TransformException | FactoryException e) {
- throw new BackingStoreException(e);
- }
- })
- .orElse(in);
- } catch (BackingStoreException e) {
- throw e.unwrapOrRethrow(TransformException.class);
- }
- }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/Capabilities.java b/core/sis-feature/src/main/java/org/apache/sis/filter/Capabilities.java
index 1f4d842..e645968 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/Capabilities.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/Capabilities.java
@@ -16,422 +16,139 @@
*/
package org.apache.sis.filter;
+import java.util.Set;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.apache.sis.internal.util.UnmodifiableArrayList;
-import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
-import org.opengis.filter.Filter;
+import java.util.Optional;
+import org.opengis.util.LocalName;
+import org.apache.sis.util.collection.CodeListSet;
+import org.apache.sis.internal.feature.AttributeConvention;
+
+// Branch-dependent imports
+import org.opengis.filter.ComparisonOperatorName;
+import org.opengis.filter.capability.Conformance;
+import org.opengis.filter.capability.IdCapabilities;
+import org.opengis.filter.capability.FilterCapabilities;
+import org.opengis.filter.capability.ScalarCapabilities;
+import org.opengis.filter.capability.SpatialCapabilities;
+import org.opengis.filter.capability.TemporalCapabilities;
+
/**
- * Filter capabilities describing classes.
+ * Metadata about the specific elements that Apache SIS implementation supports.
+ *
+ * @todo Missing {@link SpatialCapabilities} and {@link TemporalCapabilities}.
*
- * @author Johann Sorel (Geomatys)
- * @version 2.0
- * @since 2.0
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since 1.1
* @module
*/
-abstract class Capabilities {
-
+final class Capabilities implements FilterCapabilities, Conformance, IdCapabilities, ScalarCapabilities {
+ /**
+ * The unique instance of the capabilities document.
+ */
+ static final Capabilities INSTANCE = new Capabilities();
+
+ /**
+ * Creates a new capability document.
+ */
private Capabilities() {
}
- static final class FilterCapabilities implements org.opengis.filter.capability.FilterCapabilities {
-
- private final String version;
- private final org.opengis.filter.capability.IdCapabilities id;
- private final org.opengis.filter.capability.SpatialCapabilities spatial;
- private final org.opengis.filter.capability.ScalarCapabilities scalar;
- private final org.opengis.filter.capability.TemporalCapabilities temporal;
-
- FilterCapabilities(final String version,
- final org.opengis.filter.capability.IdCapabilities id,
- final org.opengis.filter.capability.SpatialCapabilities spatial,
- final org.opengis.filter.capability.ScalarCapabilities scalar,
- final org.opengis.filter.capability.TemporalCapabilities temporal) {
- this.version = version;
- this.id = id;
- this.spatial = spatial;
- this.scalar = scalar;
- this.temporal = temporal;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public org.opengis.filter.capability.ScalarCapabilities getScalarCapabilities() {
- return scalar;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public org.opengis.filter.capability.SpatialCapabilities getSpatialCapabilities() {
- return spatial;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public org.opengis.filter.capability.TemporalCapabilities getTemporalCapabilities() {
- return temporal;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public org.opengis.filter.capability.IdCapabilities getIdCapabilities() {
- return id;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public String getVersion() {
- return version;
- }
-
- public boolean supports(final Filter filter) {
- throw new UnsupportedOperationException();
- }
- }
-
- static final class IdCapabilities implements org.opengis.filter.capability.IdCapabilities {
-
- private final boolean eid;
- private final boolean fid;
-
- public IdCapabilities(final boolean eid, final boolean fid) {
- this.eid = eid;
- this.fid = fid;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public boolean hasEID() {
- return eid;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public boolean hasFID() {
- return fid;
- }
+ /**
+ * Declaration of which conformance classes a particular implementation supports.
+ */
+ @Override
+ public Conformance getConformance() {
+ return this;
+ }
+
+ /**
+ * Returns whether the implementation supports the <cite>Resource Identification</cite> conformance level.
+ */
+ @Override
+ public boolean implementsResourceld() {
+ return true;
+ }
+
+ /**
+ * Provides capabilities used to convey supported identifier operators.
+ */
+ @Override
+ public Optional<IdCapabilities> getIdCapabilities() {
+ return Optional.of(this);
+ }
+
+ /**
+ * Declares the names of the properties used for resource identifiers.
+ */
+ @Override
+ public Collection<LocalName> getResourceIdentifiers() {
+ return Collections.singleton(AttributeConvention.IDENTIFIER_PROPERTY.tip());
+ }
+
+ /**
+ * Returns whether the implementation supports the <cite>Standard Filter</cite> conformance level.
+ * A value of {@code true} means that all the logical operators ({@code And}, {@code Or}, {@code Not})
+ * are supported, together with all the standard {@link ComparisonOperatorName}. Those operators shall
+ * be listed in the {@linkplain FilterCapabilities#getScalarCapabilities() scalar capabilities}.
+ */
+ @Override
+ public boolean implementsStandardFilter() {
+ return true;
+ }
+
+ /**
+ * Indicates that SIS supports <cite>And</cite>, <cite>Or</cite> and <cite>Not</cite> operators.
+ */
+ @Override
+ public boolean hasLogicalOperators() {
+ return true;
+ }
+
+ /**
+ * Advertises which logical, comparison and arithmetic operators the service supports.
+ */
+ @Override
+ public Optional<ScalarCapabilities> getScalarCapabilities() {
+ return Optional.of(this);
+ }
+
+ /**
+ * Advertises that SIS supports all comparison operators.
+ */
+ @Override
+ public Set<ComparisonOperatorName> getComparisonOperators() {
+ return new CodeListSet<>(ComparisonOperatorName.class, true);
+ }
+
+ /**
+ * Indicates that Apache SIS supports the <cite>Spatial Filter</cite> conformance level.
+ *
+ * @todo Need to implement {@linkplain FilterCapabilities#getSpatialCapabilities() temporal capabilities}.
+ */
+ @Override
+ public boolean implementsSpatialFilter() {
+ return true;
+ }
+
+ /**
+ * Indicates that Apache SIS supports the <cite>Temporal Filter</cite> conformance level.
+ *
+ * @todo Need to implement {@linkplain FilterCapabilities#getTemporalCapabilities() temporal capabilities}.
+ */
+ @Override
+ public boolean implementsTemporalFilter() {
+ return true;
+ }
+
+ /**
+ * Indicates that Apache SIS supports the <cite>Sorting</cite> conformance level.
+ */
+ @Override
+ public boolean implementsSorting() {
+ return true;
}
-
- static final class ScalarCapabilities implements org.opengis.filter.capability.ScalarCapabilities {
-
- private final boolean logical;
- private final org.opengis.filter.capability.ComparisonOperators comparisons;
- private final org.opengis.filter.capability.ArithmeticOperators arithmetics;
-
- public ScalarCapabilities(final boolean logical,
- final org.opengis.filter.capability.ComparisonOperators comparisons,
- final org.opengis.filter.capability.ArithmeticOperators arithmetics) {
- this.logical = logical;
- this.comparisons = comparisons;
- this.arithmetics = arithmetics;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public boolean hasLogicalOperators() {
- return logical;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public org.opengis.filter.capability.ComparisonOperators getComparisonOperators() {
- return comparisons;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public org.opengis.filter.capability.ArithmeticOperators getArithmeticOperators() {
- return arithmetics;
- }
- }
-
- static final class SpatialCapabilities implements org.opengis.filter.capability.SpatialCapabilities {
-
- private final List<org.opengis.filter.capability.GeometryOperand> operands;
- private final org.opengis.filter.capability.SpatialOperators operators;
-
- public SpatialCapabilities(final org.opengis.filter.capability.GeometryOperand[] operands, final org.opengis.filter.capability.SpatialOperators operators) {
- ensureNonNull("operands", operands);
- ensureNonNull("spatial operators", operators);
- if (operands.length == 0) {
- throw new IllegalArgumentException("Operands must not be empty");
- }
-
- this.operands = UnmodifiableArrayList.wrap(operands);
- this.operators = operators;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public Collection<org.opengis.filter.capability.GeometryOperand> getGeometryOperands() {
- return operands;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public org.opengis.filter.capability.SpatialOperators getSpatialOperators() {
- return operators;
- }
- }
-
- static final class TemporalCapabilities implements org.opengis.filter.capability.TemporalCapabilities {
-
- private final Collection<org.opengis.filter.capability.TemporalOperand> operands;
- private final org.opengis.filter.capability.TemporalOperators operators;
-
- TemporalCapabilities(final org.opengis.filter.capability.TemporalOperand[] operands,
- final org.opengis.filter.capability.TemporalOperators operators) {
- ensureNonNull("operands", operands);
- ensureNonNull("temporal operators", operators);
- if (operands == null) {
- throw new IllegalArgumentException("Operands must not be null");
- }
-
- this.operands = UnmodifiableArrayList.wrap(operands);
- this.operators = operators;
- }
-
- @Override
- public Collection<org.opengis.filter.capability.TemporalOperand> getTemporalOperands() {
- return operands;
- }
-
- @Override
- public org.opengis.filter.capability.TemporalOperators getTemporalOperators() {
- return operators;
- }
- }
-
- static final class FunctionName implements org.opengis.filter.capability.FunctionName {
-
- private final String name;
- private final List<String> argNames;
- private final int size;
-
- FunctionName(final String name, final List<String> argNames, final int size) {
- this.name = name;
- this.argNames = argNames;
- this.size = size;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public int getArgumentCount() {
- return size;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public List<String> getArgumentNames() {
- return argNames;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public String getName() {
- return name;
- }
- }
-
- static final class Functions implements org.opengis.filter.capability.Functions {
-
- private final Map<String, org.opengis.filter.capability.FunctionName> functions = new HashMap<>();
-
- public Functions(final org.opengis.filter.capability.FunctionName[] functions) {
- if (functions == null) {
- throw new IllegalArgumentException("Functions must not be null");
- }
- for (org.opengis.filter.capability.FunctionName fn : functions) {
- this.functions.put(fn.getName(), fn);
- }
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public Collection<org.opengis.filter.capability.FunctionName> getFunctionNames() {
- return functions.values();
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public org.opengis.filter.capability.FunctionName getFunctionName(final String name) {
- return functions.get(name);
- }
- }
-
- private abstract static class Operators<T extends org.opengis.filter.capability.Operator> {
-
- private final Map<String, T> operators = new HashMap<>();
-
- Operators(final T[] operators) {
- if (operators == null || operators.length == 0) {
- throw new IllegalArgumentException("Functions must not be null or empty");
- }
- for (T op : operators) {
- this.operators.put(op.getName(), op);
- }
- }
-
- public Collection<T> getOperators() {
- return Collections.unmodifiableCollection(operators.values());
- }
-
- public T getOperator(final String name) {
- return operators.get(name);
- }
- }
-
- static final class ArithmeticOperators implements org.opengis.filter.capability.ArithmeticOperators {
-
- private final boolean simple;
- private final org.opengis.filter.capability.Functions functions;
-
- public ArithmeticOperators(final boolean simple, final org.opengis.filter.capability.Functions functions) {
- ensureNonNull("functions", functions);
- this.simple = simple;
- this.functions = functions;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public boolean hasSimpleArithmetic() {
- return simple;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public org.opengis.filter.capability.Functions getFunctions() {
- return functions;
- }
- }
-
- static final class ComparisonOperators extends Operators<org.opengis.filter.capability.Operator> implements org.opengis.filter.capability.ComparisonOperators {
-
- ComparisonOperators(final org.opengis.filter.capability.Operator[] operators) {
- super(operators);
- }
- }
-
- static final class SpatialOperators extends Operators<org.opengis.filter.capability.SpatialOperator> implements org.opengis.filter.capability.SpatialOperators {
-
- public SpatialOperators(final org.opengis.filter.capability.SpatialOperator[] operators) {
- super(operators);
- }
- }
-
- static final class TemporalOperators extends Operators<org.opengis.filter.capability.TemporalOperator> implements org.opengis.filter.capability.TemporalOperators {
-
- public TemporalOperators(final TemporalOperator[] operators) {
- super(operators);
- }
- }
-
- static class Operator implements org.opengis.filter.capability.Operator {
-
- private final String name;
-
- public Operator(final String name) {
- ensureNonNull("operator name", name);
- this.name = name;
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public String getName() {
- return name;
- }
- }
-
- static final class SpatialOperator extends Operator implements org.opengis.filter.capability.SpatialOperator {
-
- private final List<org.opengis.filter.capability.GeometryOperand> operands;
-
- public SpatialOperator(final String name, final org.opengis.filter.capability.GeometryOperand[] operands) {
- super(name);
-
- if (operands == null || operands.length == 0) {
- throw new IllegalArgumentException("Operands list can not be null or empty");
- }
-
- //use a threadsafe optimized immutable list
- this.operands = UnmodifiableArrayList.wrap(operands.clone());
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public Collection<org.opengis.filter.capability.GeometryOperand> getGeometryOperands() {
- return operands;
- }
- }
-
- static final class TemporalOperator extends Operator implements org.opengis.filter.capability.TemporalOperator {
-
- private final List<org.opengis.filter.capability.TemporalOperand> operands;
-
- public TemporalOperator(final String name, final org.opengis.filter.capability.TemporalOperand[] operands) {
- super(name);
-
- if (operands == null || operands.length == 0) {
- throw new IllegalArgumentException("Operands list can not be null or empty");
- }
-
- //use a threadsafe optimized immutable list
- this.operands = UnmodifiableArrayList.wrap(operands.clone());
- }
-
- /**
- * {@inheritDoc }
- */
- @Override
- public Collection<org.opengis.filter.capability.TemporalOperand> getTemporalOperands() {
- return operands;
- }
- }
-
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java
index 6218eeb..5506a67 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java
@@ -18,6 +18,7 @@ package org.apache.sis.filter;
import java.math.BigDecimal;
import java.math.BigInteger;
+import java.util.List;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
@@ -39,10 +40,12 @@ import org.apache.sis.math.Fraction;
import org.apache.sis.util.ArgumentChecks;
// Branch-dependent imports
+import org.opengis.filter.Filter;
+import org.opengis.filter.Expression;
import org.opengis.filter.MatchAction;
-import org.opengis.filter.expression.Expression;
+import org.opengis.filter.ComparisonOperatorName;
import org.opengis.filter.BinaryComparisonOperator;
-import org.opengis.filter.FilterVisitor;
+import org.opengis.filter.BetweenComparisonOperator;
/**
@@ -67,10 +70,15 @@ import org.opengis.filter.FilterVisitor;
* @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}) used as inputs.
+ *
+ * @since 1.1
* @module
*/
-abstract class ComparisonFunction extends BinaryFunction implements BinaryComparisonOperator {
+abstract class ComparisonFunction<R> extends BinaryFunction<R,Object,Object>
+ implements BinaryComparisonOperator<R>, Optimization.OnFilter<R>
+{
/**
* For cross-version compatibility.
*/
@@ -79,13 +87,13 @@ abstract class ComparisonFunction extends BinaryFunction implements BinaryCompar
/**
* Specifies whether comparisons are case sensitive.
*/
- private final boolean isMatchingCase;
+ protected final boolean isMatchingCase;
/**
* Specifies how the comparisons shall be evaluated for a collection of values.
* Values can be ALL, ANY or ONE.
*/
- private final MatchAction matchAction;
+ protected final MatchAction matchAction;
/**
* Creates a new comparator.
@@ -95,7 +103,10 @@ abstract class ComparisonFunction extends BinaryFunction implements BinaryCompar
* @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;
@@ -103,6 +114,24 @@ abstract class ComparisonFunction extends BinaryFunction implements BinaryCompar
}
/**
+ * 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}.
+ */
+ @Override
+ public final Expression<? super R, ?> getOperand2() {
+ return expression2;
+ }
+
+ /**
* Returns whether comparisons are case sensitive.
*/
@Override
@@ -132,7 +161,7 @@ abstract class ComparisonFunction extends BinaryFunction implements BinaryCompar
@Override
public final boolean equals(final Object obj) {
if (super.equals(obj)) {
- final ComparisonFunction other = (ComparisonFunction) obj;
+ final ComparisonFunction<?> other = (ComparisonFunction<?>) obj;
return other.isMatchingCase == isMatchingCase && matchAction.equals(other.matchAction);
}
return false;
@@ -144,10 +173,10 @@ abstract class ComparisonFunction extends BinaryFunction implements BinaryCompar
* or at most one expression can produce a collection.
*/
@Override
- public final boolean evaluate(final Object candidate) {
- final Object left = expression1.evaluate(candidate);
+ public final boolean test(final R candidate) {
+ final Object left = expression1.apply(candidate);
if (left != null) {
- final Object right = expression2.evaluate(candidate);
+ final Object right = expression2.apply(candidate);
if (right != null) {
final Iterable<?> collection;
final boolean collectionFirst = (left instanceof Iterable<?>);
@@ -511,19 +540,29 @@ abstract class ComparisonFunction extends BinaryFunction implements BinaryCompar
/**
- * 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. */
+ @Override public ComparisonOperatorName getOperatorType() {
+ return ComparisonOperatorName.PROPERTY_IS_LESS_THAN;
+ }
@Override protected char symbol() {return '<';}
/** Converts {@link Comparable#compareTo(Object)} result to this filter result. */
@@ -537,28 +576,33 @@ abstract class ComparisonFunction extends BinaryFunction implements BinaryCompar
@Override protected boolean compare (ChronoLocalDate left, ChronoLocalDate right) {return left.isBefore(right);}
@Override protected boolean compare (ChronoLocalDateTime<?> left, ChronoLocalDateTime<?> right) {return left.isBefore(right);}
@Override protected boolean compare (ChronoZonedDateTime<?> left, ChronoZonedDateTime<?> right) {return left.isBefore(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} (≤) filter.
+ * The {@code "PropertyIsLessThanOrEqualTo"} (≤) filter.
*/
- static final class LessThanOrEqualTo extends ComparisonFunction implements org.opengis.filter.PropertyIsLessThanOrEqualTo {
+ static final class LessThanOrEqualTo<R> extends ComparisonFunction<R> {
/** For cross-version compatibility during (de)serialization. */
private static final long serialVersionUID = 6357459227911760871L;
- /** Creates a new filter for the {@value #NAME} operation. */
- LessThanOrEqualTo(Expression expression1, Expression expression2, boolean isMatchingCase, MatchAction matchAction) {
+ /** Creates a new filter. */
+ LessThanOrEqualTo(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 LessThanOrEqualTo<>(effective[0], effective[1], isMatchingCase, matchAction);
+ }
+
+ /** Identification of the this operation. */
+ @Override public ComparisonOperatorName getOperatorType() {
+ return ComparisonOperatorName.PROPERTY_IS_LESS_THAN_OR_EQUAL_TO;
+ }
@Override protected char symbol() {return '≤';}
/** Converts {@link Comparable#compareTo(Object)} result to this filter result. */
@@ -572,28 +616,33 @@ abstract class ComparisonFunction extends BinaryFunction implements BinaryCompar
@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. */
+ @Override public ComparisonOperatorName getOperatorType() {
+ return ComparisonOperatorName.PROPERTY_IS_GREATER_THAN;
+ }
@Override protected char symbol() {return '>';}
/** Converts {@link Comparable#compareTo(Object)} result to this filter result. */
@@ -607,28 +656,33 @@ abstract class ComparisonFunction extends BinaryFunction implements BinaryCompar
@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} (≥) filter.
+ * The {@code "PropertyIsGreaterThanOrEqualTo"} (≥) filter.
*/
- static final class GreaterThanOrEqualTo extends ComparisonFunction implements org.opengis.filter.PropertyIsGreaterThanOrEqualTo {
+ static final class GreaterThanOrEqualTo<R> extends ComparisonFunction<R> {
/** For cross-version compatibility during (de)serialization. */
private static final long serialVersionUID = 1514185657159141882L;
- /** Creates a new filter for the {@value #NAME} operation. */
- GreaterThanOrEqualTo(Expression expression1, Expression expression2, boolean isMatchingCase, MatchAction matchAction) {
+ /** Creates a new filter. */
+ GreaterThanOrEqualTo(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 GreaterThanOrEqualTo<>(effective[0], effective[1], isMatchingCase, matchAction);
+ }
+
+ /** Identification of the this operation. */
+ @Override public ComparisonOperatorName getOperatorType() {
+ return ComparisonOperatorName.PROPERTY_IS_GREATER_THAN_OR_EQUAL_TO;
+ }
@Override protected char symbol() {return '≥';}
/** Converts {@link Comparable#compareTo(Object)} result to this filter result. */
@@ -642,28 +696,33 @@ abstract class ComparisonFunction extends BinaryFunction implements BinaryCompar
@Override protected boolean compare (ChronoLocalDate left, ChronoLocalDate right) {return !left.isBefore(right);}
@Override protected boolean compare (ChronoLocalDateTime<?> left, ChronoLocalDateTime<?> right) {return !left.isBefore(right);}
@Override protected boolean compare (ChronoZonedDateTime<?> left, ChronoZonedDateTime<?> right) {return !left.isBefore(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} (=) filter.
+ * The {@code "PropertyIsEqualTo"} (=) filter.
*/
- static final class EqualTo extends ComparisonFunction implements org.opengis.filter.PropertyIsEqualTo {
+ static final class EqualTo<R> extends ComparisonFunction<R> {
/** For cross-version compatibility during (de)serialization. */
private static final long serialVersionUID = 8502612221498749667L;
- /** Creates a new filter for the {@value #NAME} operation. */
- EqualTo(Expression expression1, Expression expression2, boolean isMatchingCase, MatchAction matchAction) {
+ /** Creates a new filter. */
+ EqualTo(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 EqualTo<>(effective[0], effective[1], isMatchingCase, matchAction);
+ }
+
+ /** Identification of the this operation. */
+ @Override public ComparisonOperatorName getOperatorType() {
+ return ComparisonOperatorName.PROPERTY_IS_EQUAL_TO;
+ }
@Override protected char symbol() {return '=';}
/** Converts {@link Comparable#compareTo(Object)} result to this filter result. */
@@ -677,28 +736,33 @@ abstract class ComparisonFunction extends BinaryFunction implements BinaryCompar
@Override protected boolean compare (ChronoLocalDate left, ChronoLocalDate right) {return left.isEqual(right);}
@Override protected boolean compare (ChronoLocalDateTime<?> left, ChronoLocalDateTime<?> right) {return left.isEqual(right);}
@Override protected boolean compare (ChronoZonedDateTime<?> left, ChronoZonedDateTime<?> right) {return left.isEqual(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} (≠) filter.
+ * The {@code "PropertyIsNotEqualTo"} (≠) filter.
*/
- static final class NotEqualTo extends ComparisonFunction implements org.opengis.filter.PropertyIsNotEqualTo {
+ static final class NotEqualTo<R> extends ComparisonFunction<R> {
/** For cross-version compatibility during (de)serialization. */
private static final long serialVersionUID = -3295957142249035362L;
- /** Creates a new filter for the {@value #NAME} operation. */
- NotEqualTo(Expression expression1, Expression expression2, boolean isMatchingCase, MatchAction matchAction) {
+ /** Creates a new filter. */
+ NotEqualTo(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 NotEqualTo<>(effective[0], effective[1], isMatchingCase, matchAction);
+ }
+
+ /** Identification of the this operation. */
+ @Override public ComparisonOperatorName getOperatorType() {
+ return ComparisonOperatorName.PROPERTY_IS_NOT_EQUAL_TO;
+ }
@Override protected char symbol() {return '≠';}
/** Converts {@link Comparable#compareTo(Object)} result to this filter result. */
@@ -712,31 +776,30 @@ abstract class ComparisonFunction extends BinaryFunction implements BinaryCompar
@Override protected boolean compare (ChronoLocalDate left, ChronoLocalDate right) {return !left.isEqual(right);}
@Override protected boolean compare (ChronoLocalDateTime<?> left, ChronoLocalDateTime<?> right) {return !left.isEqual(right);}
@Override protected boolean compare (ChronoZonedDateTime<?> left, ChronoZonedDateTime<?> right) {return !left.isEqual(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} filter. This can be seen as a specialization of
+ * The {@code "PropertyIsBetween"} filter. This can be seen as a specialization of
* {@link org.apache.sis.filter.LogicalFunction.And} when one expression is
* {@link LessThanOrEqualTo} and a second expression is {@link GreaterThanOrEqualTo}.
*
* @see org.apache.sis.filter.LogicalFunction.And
*/
- static final class Between extends Node implements org.opengis.filter.PropertyIsBetween {
+ static final class Between<R> extends Node implements BetweenComparisonOperator<R> {
/** For cross-version compatibility during (de)serialization. */
private static final long serialVersionUID = -2434954008425799595L;
- /** The first operation to apply. */ private final GreaterThanOrEqualTo lower;
- /** The second operation to apply. */ private final LessThanOrEqualTo upper;
+ /** The first operation to apply. */ private final GreaterThanOrEqualTo<? super R> lower;
+ /** The second operation to apply. */ private final LessThanOrEqualTo<? super R> upper;
- /** Creates a new filter for the {@value #NAME} operation. */
- Between(final Expression expression, final Expression lower, final Expression upper) {
- this.lower = new GreaterThanOrEqualTo(expression, lower, true, MatchAction.ANY);
- this.upper = new LessThanOrEqualTo(expression, upper, true, MatchAction.ANY);
+ /** Creates a new filter. */
+ Between(final Expression<? super R, ?> expression,
+ final Expression<? super R, ?> lower,
+ final Expression<? super R, ?> upper)
+ {
+ this.lower = new GreaterThanOrEqualTo<>(expression, lower, true, MatchAction.ANY);
+ this.upper = new LessThanOrEqualTo<>(expression, upper, true, MatchAction.ANY);
}
/**
@@ -744,23 +807,22 @@ abstract class ComparisonFunction extends BinaryFunction implements BinaryCompar
* is the same as {@code upper.expression1}, that repetition is omitted.
*/
@Override protected Collection<?> getChildren() {
- return Arrays.asList(lower.expression1, lower.expression2, upper.expression2);
+ return getExpressions();
}
- /** Identification of this operation. */
- @Override public String getName() {return NAME;}
- @Override public Expression getExpression() {return lower.expression1;}
- @Override public Expression getLowerBoundary() {return lower.expression2;}
- @Override public Expression getUpperBoundary() {return upper.expression2;}
-
- /** Execute the filter like and AND operation. */
- @Override public boolean evaluate(final Object object) {
- return lower.evaluate(object) && upper.evaluate(object);
+ /** Returns the expression to be compared by this operator, together with boundaries. */
+ @Override public List<Expression<? super R, ?>> getExpressions() {
+ return Arrays.asList(lower.expression1, lower.expression2, upper.expression2);
}
- /** Implementation of the visitor pattern (not used by Apache SIS). */
- @Override public Object accept(FilterVisitor visitor, Object extraData) {
- return visitor.visit(this, extraData);
+ /** Returns the expression to be compared. */
+ @Override public Expression<? super R, ?> getExpression() {return lower.expression1;}
+ @Override public Expression<? super R, ?> getLowerBoundary() {return lower.expression2;}
+ @Override public Expression<? super R, ?> getUpperBoundary() {return upper.expression2;}
+
+ /** Executes the filter operation. */
+ @Override public boolean test(final R object) {
+ return lower.test(object) && upper.test(object);
}
}
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ConvertFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ConvertFunction.java
new file mode 100644
index 0000000..09aef04
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ConvertFunction.java
@@ -0,0 +1,197 @@
+/*
+ * 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.Arrays;
+import java.util.Collection;
+import org.opengis.util.ScopedName;
+import org.apache.sis.util.ObjectConverter;
+import org.apache.sis.util.ObjectConverters;
+import org.apache.sis.util.UnconvertibleObjectException;
+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.internal.feature.FeatureExpression;
+import org.apache.sis.util.resources.Errors;
+
+// Branch-dependent imports
+import org.opengis.filter.Expression;
+import org.opengis.feature.FeatureType;
+
+
+/**
+ * Expression whose results are converted to a different type.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ *
+ * @param <R> the type of resources (e.g. {@link org.opengis.feature.Feature}) used as inputs.
+ * @param <S> the type of value computed by the wrapped exception. This is the type to convert.
+ * @param <V> the type of value computed by this expression. This is the type after conversion.
+ *
+ * @see GeometryConverter
+ *
+ * @since 1.1
+ * @module
+ */
+final class ConvertFunction<R,S,V> extends UnaryFunction<R,S>
+ implements FeatureExpression<R,V>, Optimization.OnExpression<R,V>
+{
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 4686604324414717316L;
+
+ /**
+ * Name of this expression.
+ */
+ private static final ScopedName NAME = createName("Convert");
+
+ /**
+ * The converter to use.
+ */
+ private final ObjectConverter<? super S, ? extends V> converter;
+
+ /**
+ * Creates a new converted expression.
+ *
+ * @param expression the expression providing source values.
+ * @param source the type of value produced by given expression
+ * @param target the desired type for the expression result.
+ * @throws UnconvertibleObjectException if no converter is found.
+ */
+ ConvertFunction(final Expression<? super R, ? extends S> expression, final Class<S> source, final Class<V> target) {
+ super(expression);
+ converter = ObjectConverters.find(source, target);
+ }
+
+ /**
+ * Creates a new converted expression after optimization.
+ *
+ * @param expression the expression providing source values.
+ * @throws UnconvertibleObjectException if no converter is found.
+ */
+ private ConvertFunction(final ConvertFunction<R,S,V> original, final Expression<? super R, ? extends S> expression) {
+ super(expression);
+ converter = original.converter;
+ }
+
+ /**
+ * Creates a new expression of the same type than this expression, but with optimized parameters.
+ */
+ @Override
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public Expression<R,V> recreate(Expression<? super R, ?>[] effective) {
+ final Expression<? super R, ?> e = effective[0];
+ if (e instanceof FeatureExpression<?,?>) {
+ final Class<? extends V> target = getValueClass(); // This is <V>.
+ final Class<?> source = ((FeatureExpression<?,?>) e).getValueClass(); // May become <S>.
+ if (target.isAssignableFrom(source)) {
+ return (Expression<R,V>) e;
+ }
+ if (source != Object.class) {
+ return new ConvertFunction(e, source, target);
+ }
+ }
+ final Class<? super S> source = converter.getSourceClass();
+ return new ConvertFunction(this, e.toValueType(source));
+ }
+
+ /**
+ * Returns an identification of this operation.
+ */
+ @Override
+ public ScopedName getFunctionName() {
+ return NAME;
+ }
+
+ /**
+ * Returns the singleton expression tested by this operator
+ * together with the source and target classes.
+ */
+ @Override
+ protected Collection<?> getChildren() {
+ return Arrays.asList(expression, converter.getSourceClass(), converter.getTargetClass());
+ }
+
+ /**
+ * Evaluates the expression for producing a result of the given type.
+ * If this method can not produce a value of the given type, then it returns {@code null}.
+ * This implementation evaluates the expression {@linkplain Expression#apply(Object) in the default way},
+ * then tries to convert the result to the target type.
+ *
+ * @param feature the value or feature to evaluate with this expression.
+ * @return the result, or {@code null} if it can not be of the specified type.
+ */
+ @Override
+ public V apply(final R feature) {
+ final S value = expression.apply(feature);
+ try {
+ return converter.apply(value);
+ } catch (UnconvertibleObjectException e) {
+ warning(e, false);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the type of values computed by this expression.
+ */
+ @Override
+ public Class<? extends V> getValueClass() {
+ return converter.getTargetClass();
+ }
+
+ /**
+ * Provides the expected type of values produced by this expression
+ * when a feature of the given type is evaluated.
+ */
+ @Override
+ public PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
+ final PropertyTypeBuilder p = FeatureExpression.expectedType(expression, valueType, addTo);
+ if (p instanceof AttributeTypeBuilder<?>) {
+ return ((AttributeTypeBuilder<?>) p).setValueClass(getValueClass());
+ }
+ return p;
+ }
+
+ /**
+ * Returns an expression doing the same evaluation than this method, but returning results as values
+ * of the specified type. The result may be {@code this}.
+ *
+ * @param <N> compile-time value of {@code type}.
+ * @param type desired type of expression results.
+ * @return expression doing the same operation this this expression but with results of the specified type.
+ * @throws ClassCastException if the specified type is not a target type supported by implementation.
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public <N> Expression<R,N> toValueType(final Class<N> type) {
+ if (type.isAssignableFrom(getValueClass())) {
+ return (Expression<R,N>) this;
+ }
+ final Class<? super S> source = converter.getSourceClass();
+ if (type.isAssignableFrom(source)) {
+ return (Expression<R,N>) expression;
+ } else try {
+ return new ConvertFunction<>(expression, source, type);
+ } catch (UnconvertibleObjectException e) {
+ throw (ClassCastException) new ClassCastException(Errors.format(
+ Errors.Keys.CanNotConvertValue_2, expression.getFunctionName(), type)).initCause(e);
+ }
+ }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultBBOX.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultBBOX.java
deleted file mode 100644
index c95d1e8..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultBBOX.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * 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.io.IOException;
-import java.io.Serializable;
-import java.util.Objects;
-import java.util.function.Predicate;
-
-import org.opengis.feature.AttributeType;
-import org.opengis.feature.Feature;
-import org.opengis.feature.FeatureType;
-import org.opengis.filter.FilterVisitor;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.Literal;
-import org.opengis.filter.spatial.BBOX;
-import org.opengis.geometry.Envelope;
-import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.opengis.referencing.operation.CoordinateOperation;
-import org.opengis.referencing.operation.TransformException;
-
-import org.apache.sis.feature.Features;
-import org.apache.sis.geometry.AbstractEnvelope;
-import org.apache.sis.geometry.Envelopes;
-import org.apache.sis.geometry.GeneralEnvelope;
-import org.apache.sis.geometry.ImmutableEnvelope;
-import org.apache.sis.internal.feature.Geometries;
-import org.apache.sis.util.Classes;
-import org.apache.sis.util.NullArgumentException;
-import org.apache.sis.util.collection.BackingStoreException;
-
-/**
- * @implNote AMBIGUITY : Description of BBOX operator from <a href="http://docs.opengeospatial.org/is/09-026r2/09-026r2.html#60">
- * filter encoding 2.0.2</a> is rather succinct, and do not well explain if both tested expressions must be
- * envelopes, or if we should test an envelope against a real geometry. What we will do in this implementation is
- * testing bbox only, because the test for a bbox against a complex geometry can be realized using ST_Intersect
- * operator.
- *
- * Border management: From above reference, bbox should be equivalent to Not ST_Disjoint (from SQLMM/ISO:19125).
- * Disjoint operation specifies that both geometry boundaries must not touch (their intersection is an empty space), so
- * we will consider as valid envelopes with only a common boundary.
- *
- * TODO: CRS check.
- */
-final class DefaultBBOX extends SpatialFunction implements BBOX, Serializable {
-
- private static final long serialVersionUID = 3068335120981348484L;
-
- final Expression left;
- final Expression right;
-
- private transient Predicate intersects;
-
- DefaultBBOX(Expression left, Expression right) {
- super(left == null ? Expression.NIL : left, right == null ? Expression.NIL : right);
- this.left = left == Expression.NIL ? null : left;
- this.right = right == Expression.NIL ? null : right;
- init();
- }
-
- /**
- * Initialize this filter state. It is necessary because of serialization compliance.
- */
- private void init() {
- if (left == null && right == null) {
- throw new NullArgumentException(
- "Both arguments are null, but at least one must be given " +
- "(as stated in OGC Filter encoding corrigendum 2.0.2, section 7.8.3.2)."
- );
- }
-
- if (left instanceof Literal) {
- intersects = asOptimizedTest((Literal) left, right);
- } else if (right instanceof Literal) {
- intersects = asOptimizedTest((Literal) right, left);
- } else intersects = this::nonOptimizedIntersect;
- }
-
- @Override
- public boolean evaluate(Object object) {
- return intersects.test(object);
- }
-
- @Override
- public Object accept(FilterVisitor visitor, Object extraData) {
- return visitor.visit(this, extraData);
- }
-
- private boolean nonOptimizedIntersect(Object candidate) {
- Envelope leftEval = left == null ? null : asEnvelope(left, candidate);
- Envelope rightEval = right == null ? null : asEnvelope(right, candidate);
- if (left == null) {
- return multiIntersect(candidate, rightEval);
- } else if (right == null) {
- return multiIntersect(candidate, leftEval);
- }
-
- /* OGC Filter encoding corrigendum 2.0.2 section 7.8.3.4 states that false must be returned if any of the
- * operand is null. It does not state what to do if both are null, but we'll follow the same behavior.
- */
- if (leftEval == null || rightEval == null) return false;
- return intersect(AbstractEnvelope.castOrCopy(leftEval), rightEval);
- }
-
- private static boolean intersect(final Object candidate, final Expression valueExtractor, final AbstractEnvelope constEnvelope) {
- final Envelope candidateEnv = asEnvelope(valueExtractor, candidate);
- if (candidateEnv == null) return false;
- return intersect(constEnvelope, candidateEnv);
- }
-
- /**
- * Ensure that all geometric properties in given candidate intersect input envelope. This method tries to match OGC
- * Filter Encoding corrigendum 2.0.2 section 7.8.3.2 that says if one of the expressions given at built is null, we
- * have to ensure all geometric properties of the candidate intersect the other expression.
- *
- * @param candidate The object to extract all geometric properties from.
- * @param fixed
- * @return
- */
- private static boolean multiIntersect(Object candidate, Envelope fixed) {
- // TODO: We could optimize by caching feature-type properties. The best way would be an initialisation
- // procedure freezing target data type, but I'm not sure such a mechanism would be possible.
- final GeneralEnvelope constEnv = GeneralEnvelope.castOrCopy(fixed);
- if (candidate instanceof Feature) {
- final Feature f = (Feature) candidate;
- final FeatureType type = f.getType();
- /* Note: for now, we could have doublons, but have no simple mean to eliminate link operations. Relying on
- * convention naming is too risky, as some drivers could use it directly on their attributes, or create a
- * computational operation (create point from numeric columns, reproject geometry, etc.). In such case, we
- * would drop valuable information.
- */
- return type.getProperties(true)
- .stream()
- .filter(p
- -> Features.toAttribute(p)
- .map(AttributeType::getValueClass)
- .filter(Geometries::isKnownType)
- .isPresent()
- )
- .map(p -> p.getName().toString())
- .map(f::getPropertyValue)
- .map(Geometries::getEnvelope)
- .allMatch(fEnv -> fEnv.isPresent() && intersect(constEnv, fEnv.get()));
- } else if (candidate instanceof Envelope) {
- return intersect(constEnv, (Envelope) candidate);
- } else {
- final Envelope env = Geometries.getEnvelope(candidate).orElseThrow(() ->
- new UnsupportedOperationException("Candidate type unsupported: " + Classes.getShortClassName(candidate)));
- return intersect(constEnv, env);
- }
- }
-
- private static Envelope asEnvelope(final Expression evaluator, final Object data) {
- Envelope eval = evaluator.evaluate(data, Envelope.class);
- if (eval == null) {
- final Object tmpVal = evaluator.evaluate(data);
- if (tmpVal instanceof Envelope) {
- eval = (Envelope) tmpVal;
- } else if (tmpVal instanceof GeographicBoundingBox) {
- eval = new GeneralEnvelope((GeographicBoundingBox) tmpVal);
- } else {
- eval = Geometries.getEnvelope(tmpVal).orElse(null);
- }
- }
- return eval;
- }
-
- private static Predicate asOptimizedTest(Literal constant, Expression other) {
- final ImmutableEnvelope constEnv = new ImmutableEnvelope(asEnvelope(constant, null));
- return other == null ? it -> multiIntersect(it, constEnv) : it -> intersect(it, other, constEnv);
- }
-
- /**
- * Ensure that given envelopes intersect, transforming them in a common suitable system if needed.
- */
- private static boolean intersect(AbstractEnvelope left, Envelope right) {
- final CRSMatching.Match bridge = CRSMatching
- .left(left.getCoordinateReferenceSystem())
- .right(right.getCoordinateReferenceSystem());
- final CoordinateOperation left2CommonOp = bridge.fromLeft().orElse(null);
- final CoordinateOperation right2CommonOp = bridge.fromRight().orElse(null);
- try {
- if (left2CommonOp != null) left = Envelopes.transform(left2CommonOp, left);
- if (right2CommonOp != null) right = Envelopes.transform(right2CommonOp, right);
- } catch (TransformException e) {
- throw new BackingStoreException(e);
- }
-
- return left.intersects(right, true); // See class doc for why true here.
- }
-
- private void readObject(java.io.ObjectInputStream stream)
- throws IOException, ClassNotFoundException {
- stream.defaultReadObject();
- init();
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- DefaultBBOX that = (DefaultBBOX) o;
- return Objects.equals(left, that.left) &&
- Objects.equals(right, that.right);
- }
-
- @Override
- public String getName() {
- return BBOX.NAME;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(left, right);
- }
-
- /*
- * DEPRECATED OPERATIONS: NOT IMPLEMENTED
- */
-
- @Override
- public String getPropertyName() {
- throw new UnsupportedOperationException("Not supported yet"); // "Alexis Manin (Geomatys)" on 08/10/2019
- }
-
- @Override
- public String getSRS() {
- throw new UnsupportedOperationException("Not supported yet"); // "Alexis Manin (Geomatys)" on 08/10/2019
- }
-
- @Override
- public double getMinX() {
- throw new UnsupportedOperationException("Not supported yet"); // "Alexis Manin (Geomatys)" on 08/10/2019
- }
-
- @Override
- public double getMinY() {
- throw new UnsupportedOperationException("Not supported yet"); // "Alexis Manin (Geomatys)" on 08/10/2019
- }
-
- @Override
- public double getMaxX() {
- throw new UnsupportedOperationException("Not supported yet"); // "Alexis Manin (Geomatys)" on 08/10/2019
- }
-
- @Override
- public double getMaxY() {
- throw new UnsupportedOperationException("Not supported yet"); // "Alexis Manin (Geomatys)" on 08/10/2019
- }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
index c516bc7..c1c5e76 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
+++ b/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
+ * than the given distance from the given geometry.
+ *
+ * @param geometry1 expression fetching the first geometry of the binary operator.
+ * @param geometry2 expression fetching the second geometry of the binary operator.
+ * @param distance minimal distance for evaluating the expression as {@code true}.
+ * @return operator that evaluates to {@code true} when all of a feature's geometry
+ * is more distant than the given distance from the second geometry.
+ *
+ * @see DistanceOperatorName#BEYOND
*/
@Override
- public PropertyIsNull isNull(final Expression expression) {
- return new UnaryFunction.IsNull(expression);
+ public DistanceOperator<R> beyond(final Expression<? super R, ? extends G> geometry1,
+ final Expression<? super R, ? extends G> geometry2,
+ final Quantity<Length> distance)
+ {
+ return new DistanceFilter<>(DistanceOperatorName.BEYOND, library, geometry1, geometry2, distance);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if any part of the first geometry lies within
+ * the given distance of the second geometry.
+ *
+ * @param geometry1 expression fetching the first geometry of the binary operator.
+ * @param geometry2 expression fetching the second geometry of the binary operator.
+ * @param distance maximal distance for evaluating the expression as {@code true}.
+ * @return operator that evaluates to {@code true} when any part of the feature's geometry
+ * lies within the given distance of the second geometry.
+ *
+ * @see DistanceOperatorName#WITHIN
*/
@Override
- public PropertyIsNil isNil(Expression expression) {
- throw new UnsupportedOperationException("Not supported yet.");
+ public DistanceOperator<R> within(final Expression<? super R, ? extends G> geometry1,
+ final Expression<? super R, ? extends G> geometry2,
+ final Quantity<Length> distance)
+ {
+ return new DistanceFilter<>(DistanceOperatorName.WITHIN, library, geometry1, geometry2, distance);
}
- // TEMPORAL FILTER /////////////////////////////////////////////////////////
-
/**
- * {@inheritDoc}
+ * Creates an operator that checks if first temporal operand is after the second.
+ *
+ * @param time1 expression fetching the first temporal value.
+ * @param time2 expression fetching the second temporal value.
+ * @return a filter for the "After" operator between the two temporal values.
+ *
+ * @see TemporalOperatorName#AFTER
*/
@Override
- public After after(Expression expression1, Expression expression2) {
- return new TemporalFunction.After(expression1, expression2);
+ public TemporalOperator<R> after(final Expression<? super R, ? extends T> time1,
+ final Expression<? super R, ? extends T> time2)
+ {
+ return new TemporalFunction.After<>(time1, time2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if first temporal operand is before the second.
+ *
+ * @param time1 expression fetching the first temporal value.
+ * @param time2 expression fetching the second temporal value.
+ * @return a filter for the "Before" operator between the two temporal values.
+ *
+ * @see TemporalOperatorName#BEFORE
*/
@Override
- public AnyInteracts anyInteracts(Expression expression1, Expression expression2) {
- return new TemporalFunction.AnyInteracts(expression1, expression2);
+ public TemporalOperator<R> before(final Expression<? super R, ? extends T> time1,
+ final Expression<? super R, ? extends T> time2)
+ {
+ return new TemporalFunction.Before<>(time1, time2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if first temporal operand begins at the second.
+ *
+ * @param time1 expression fetching the first temporal value.
+ * @param time2 expression fetching the second temporal value.
+ * @return a filter for the "Begins" operator between the two temporal values.
+ *
+ * @see TemporalOperatorName#BEGINS
*/
@Override
- public Before before(Expression expression1, Expression expression2) {
- return new TemporalFunction.Before(expression1, expression2);
+ public TemporalOperator<R> begins(final Expression<? super R, ? extends T> time1,
+ final Expression<? super R, ? extends T> time2)
+ {
+ return new TemporalFunction.Begins<>(time1, time2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if first temporal operand begun by the second.
+ *
+ * @param time1 expression fetching the first temporal value.
+ * @param time2 expression fetching the second temporal value.
+ * @return a filter for the "BegunBy" operator between the two temporal values.
+ *
+ * @see TemporalOperatorName#BEGUN_BY
*/
@Override
- public Begins begins(Expression expression1, Expression expression2) {
- return new TemporalFunction.Begins(expression1, expression2);
+ public TemporalOperator<R> begunBy(final Expression<? super R, ? extends T> time1,
+ final Expression<? super R, ? extends T> time2)
+ {
+ return new TemporalFunction.BegunBy<>(time1, time2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if first temporal operand is contained by the second.
+ *
+ * @param time1 expression fetching the first temporal value.
+ * @param time2 expression fetching the second temporal value.
+ * @return a filter for the "TContains" operator between the two temporal values.
+ *
+ * @see TemporalOperatorName#CONTAINS
*/
@Override
- public BegunBy begunBy(Expression expression1, Expression expression2) {
- return new TemporalFunction.BegunBy(expression1, expression2);
+ public TemporalOperator<R> tcontains(final Expression<? super R, ? extends T> time1,
+ final Expression<? super R, ? extends T> time2)
+ {
+ return new TemporalFunction.Contains<>(time1, time2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if first temporal operand is during the second.
+ *
+ * @param time1 expression fetching the first temporal value.
+ * @param time2 expression fetching the second temporal value.
+ * @return a filter for the "During" operator between the two temporal values.
+ *
+ * @see TemporalOperatorName#DURING
*/
@Override
- public During during(Expression expression1, Expression expression2) {
- return new TemporalFunction.During(expression1, expression2);
+ public TemporalOperator<R> during(final Expression<? super R, ? extends T> time1,
+ final Expression<? super R, ? extends T> time2)
+ {
+ return new TemporalFunction.During<>(time1, time2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if first temporal operand is equals to the second.
+ *
+ * @param time1 expression fetching the first temporal value.
+ * @param time2 expression fetching the second temporal value.
+ * @return a filter for the "TEquals" operator between the two temporal values.
+ *
+ * @see TemporalOperatorName#EQUALS
*/
@Override
- public Ends ends(Expression expression1, Expression expression2) {
- return new TemporalFunction.Ends(expression1, expression2);
+ public TemporalOperator<R> tequals(final Expression<? super R, ? extends T> time1,
+ final Expression<? super R, ? extends T> time2)
+ {
+ return new TemporalFunction.Equals<>(time1, time2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if first temporal operand overlaps the second.
+ *
+ * @param time1 expression fetching the first temporal value.
+ * @param time2 expression fetching the second temporal value.
+ * @return a filter for the "TOverlaps" operator between the two temporal values.
+ *
+ * @see TemporalOperatorName#OVERLAPS
*/
@Override
- public EndedBy endedBy(Expression expression1, Expression expression2) {
- return new TemporalFunction.EndedBy(expression1, expression2);
+ public TemporalOperator<R> toverlaps(final Expression<? super R, ? extends T> time1,
+ final Expression<? super R, ? extends T> time2)
+ {
+ return new TemporalFunction.Overlaps<>(time1, time2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if first temporal operand meets the second.
+ *
+ * @param time1 expression fetching the first temporal value.
+ * @param time2 expression fetching the second temporal value.
+ * @return a filter for the "Meets" operator between the two temporal values.
+ *
+ * @see TemporalOperatorName#MEETS
*/
@Override
- public Meets meets(Expression expression1, Expression expression2) {
- return new TemporalFunction.Meets(expression1, expression2);
+ public TemporalOperator<R> meets(final Expression<? super R, ? extends T> time1,
+ final Expression<? super R, ? extends T> time2)
+ {
+ return new TemporalFunction.Meets<>(time1, time2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if first temporal operand ends at the second.
+ *
+ * @param time1 expression fetching the first temporal value.
+ * @param time2 expression fetching the second temporal value.
+ * @return a filter for the "Ends" operator between the two temporal values.
+ *
+ * @see TemporalOperatorName#ENDS
*/
@Override
- public MetBy metBy(Expression expression1, Expression expression2) {
- return new TemporalFunction.MetBy(expression1, expression2);
+ public TemporalOperator<R> ends(final Expression<? super R, ? extends T> time1,
+ final Expression<? super R, ? extends T> time2)
+ {
+ return new TemporalFunction.Ends<>(time1, time2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if first temporal operand is overlapped by the second.
+ *
+ * @param time1 expression fetching the first temporal value.
+ * @param time2 expression fetching the second temporal value.
+ * @return a filter for the "OverlappedBy" operator between the two temporal values.
+ *
+ * @see TemporalOperatorName#OVERLAPPED_BY
*/
@Override
- public OverlappedBy overlappedBy(Expression expression1, Expression expression2) {
- return new TemporalFunction.OverlappedBy(expression1, expression2);
+ public TemporalOperator<R> overlappedBy(final Expression<? super R, ? extends T> time1,
+ final Expression<? super R, ? extends T> time2)
+ {
+ return new TemporalFunction.OverlappedBy<>(time1, time2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if first temporal operand is met by the second.
+ *
+ * @param time1 expression fetching the first temporal value.
+ * @param time2 expression fetching the second temporal value.
+ * @return a filter for the "MetBy" operator between the two temporal values.
+ *
+ * @see TemporalOperatorName#MET_BY
*/
@Override
- public TContains tcontains(Expression expression1, Expression expression2) {
- return new TemporalFunction.Contains(expression1, expression2);
+ public TemporalOperator<R> metBy(final Expression<? super R, ? extends T> time1,
+ final Expression<? super R, ? extends T> time2)
+ {
+ return new TemporalFunction.MetBy<>(time1, time2);
}
/**
- * {@inheritDoc}
+ * Creates an operator that checks if first temporal operand is ended by the second.
+ *
+ * @param time1 expression fetching the first temporal value.
+ * @param time2 expression fetching the second temporal value.
+ * @return a filter for the "EndedBy" operator between the two temporal values.
+ *
+ * @see TemporalOperatorName#ENDED_BY
*/
@Override
- public TEquals tequals(Expression expression1, Expression expression2) {
- return new TemporalFunction.Equals(expression1, expression2);
+ public TemporalOperator<R> endedBy(final Expression<? super R, ? extends T> time1,
+ final Expression<? super R, ? extends T> time2)
+ {
+ return new TemporalFunction.EndedBy<>(time1, time2);
}
/**
- * {@inheritDoc}
+ * Creates a shortcut operator semantically equivalent to NOT (Before OR Meets OR MetBy OR After).
+ * This is applicable to periods only.
+ *
+ * @param time1 expression fetching the first temporal value.
+ * @param time2 expression fetching the second temporal value.
+ * @return a filter for the "AnyInteracts" operator between the two temporal values.
+ *
+ * @see TemporalOperatorName#ANY_INTERACTS
*/
@Override
- public TOverlaps toverlaps(Expression expression1, Expression expression2) {
- return new TemporalFunction.Overlaps(expression1, expression2);
+ public TemporalOperator<R> anyInteracts(final Expression<? super R, ? extends T> time1,
+ final Expression<? super R, ? extends T> time2)
+ {
+ return new TemporalFunction.AnyInteracts<>(time1, time2);
}
- // EXPRESSIONS /////////////////////////////////////////////////////////////
-
/**
- * {@inheritDoc}
+ * Creates a function computing the numeric addition of the first and second operand.
+ *
+ * @param operand1 expression fetching the first number.
+ * @param operand2 expression fetching the second number.
+ * @return an expression for the "Add" function between the two numerical values.
+ *
+ * @todo Should we really restrict the type to {@link Number}?
*/
@Override
- public Add add(final Expression expression1, final Expression expression2) {
- return new ArithmeticFunction.Add(expression1, expression2);
+ public Expression<R,Number> add(final Expression<? super R, ? extends Number> operand1,
+ final Expression<? super R, ? extends Number> operand2)
+ {
+ return new ArithmeticFunction.Add<>(operand1, operand2);
}
/**
- * {@inheritDoc}
+ * Creates a function computing the numeric difference between the first and second operand.
+ *
+ * @param operand1 expression fetching the first number.
+ * @param operand2 expression fetching the second number.
+ * @return an expression for the "Subtract" function between the two numerical values.
+ *
+ * @todo Should we really restrict the type to {@link Number}?
*/
@Override
- public Divide divide(final Expression expression1, final Expression expression2) {
- return new ArithmeticFunction.Divide(expression1, expression2);
+ public Expression<R,Number> subtract(final Expression<? super R, ? extends Number> operand1,
+ final Expression<? super R, ? extends Number> operand2)
+ {
+ return new ArithmeticFunction.Subtract<>(operand1, operand2);
}
/**
- * {@inheritDoc}
+ * Creates a function computing the numeric product of their first and second operand.
+ *
+ * @param operand1 expression fetching the first number.
+ * @param operand2 expression fetching the second number.
+ * @return an expression for the "Multiply" function between the two numerical values.
+ *
+ * @todo Should we really restrict the type to {@link Number}?
*/
@Override
- public Multiply multiply(final Expression expression1, final Expression expression2) {
- return new ArithmeticFunction.Multiply(expression1, expression2);
+ public Expression<R,Number> multiply(final Expression<? super R, ? extends Number> operand1,
+ final Expression<? super R, ? extends Number> operand2)
+ {
+ return new ArithmeticFunction.Multiply<>(operand1, operand2);
}
/**
- * {@inheritDoc}
+ * Creates a function computing the numeric quotient resulting from dividing the first operand by the second.
+ *
+ * @param operand1 expression fetching the first number.
+ * @param operand2 expression fetching the second number.
+ * @return an expression for the "Divide" function between the two numerical values.
+ *
+ * @todo Should we really restrict the type to {@link Number}?
*/
@Override
- public Subtract subtract(final Expression expression1, final Expression expression2) {
- return new ArithmeticFunction.Subtract(expression1, expression2);
+ public Expression<R,Number> divide(final Expression<? super R, ? extends Number> operand1,
+ final Expression<? super R, ? extends Number> operand2)
+ {
+ return new ArithmeticFunction.Divide<>(operand1, operand2);
}
/**
- * {@inheritDoc}
- */
- @Override
- public Function function(final String name, final Expression... parameters) {
+ * Creates an implementation-specific function.
+ * The names of available functions is given by {@link #getCapabilities()}.
+ *
+ * @param name name of the function to call.
+ * @param parameters expressions providing values for the function arguments.
+ * @return an expression which will call the specified function.
+ * @throws IllegalArgumentException if the given name is not recognized,
+ * or if the arguments are illegal for the specified function.
+ */
+ @Override
+ public Expression<R,?> function(final String name, Expression<? super R, ?>[] parameters) {
+ ArgumentChecks.ensureNonNull("name", name);
+ ArgumentChecks.ensureNonNull("parameters", parameters);
+ parameters = parameters.clone();
+ for (int i=0; i<parameters.length; i++) {
+ ArgumentChecks.ensureNonNullElement("parameters", i, parameters[i]);
+ }
final FunctionRegister register;
- synchronized (FUNCTION_REGISTERS) {
- if (FUNCTION_REGISTERS.isEmpty()) {
+ synchronized (availableFunctions) {
+ if (availableFunctions.isEmpty()) {
/*
* Load functions when first needed or if classpath changed since last invocation.
* The SQLMM factory is hard-coded because it is considered as a basic service to
* be provided by all DefaultFilterFactory implementations, and for avoiding the
- * need to make SQLMM class public.
+ * need to make SQLMM registry class public.
*/
- final SQLMM r = new SQLMM();
+ final Registry r = new Registry(library);
for (final String fn : r.getNames()) {
- FUNCTION_REGISTERS.put(fn, r);
+ availableFunctions.put(fn, r);
}
for (final FunctionRegister er : ServiceLoader.load(FunctionRegister.class)) {
for (final String fn : er.getNames()) {
- FUNCTION_REGISTERS.putIfAbsent(fn, er);
+ availableFunctions.putIfAbsent(fn, er);
}
}
}
- register = FUNCTION_REGISTERS.get(name);
+ register = availableFunctions.get(name);
}
if (register == null) {
throw new IllegalArgumentException(Resources.format(Resources.Keys.UnknownFunction_1, name));
@@ -806,189 +994,14 @@ public class DefaultFilterFactory implements FilterFactory2 {
}
/**
- * {@inheritDoc}
- */
- @Override
- public Literal literal(final Object value) {
- return new LeafExpression.Literal(value);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Literal literal(final byte value) {
- return new LeafExpression.Literal(value);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Literal literal(final short value) {
- return new LeafExpression.Literal(value);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Literal literal(final int value) {
- return new LeafExpression.Literal(value);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Literal literal(final long value) {
- return new LeafExpression.Literal(value);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Literal literal(final float value) {
- return new LeafExpression.Literal(value);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Literal literal(final double value) {
- return new LeafExpression.Literal(value);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Literal literal(final char value) {
- return new LeafExpression.Literal(value);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Literal literal(final boolean value) {
- return new LeafExpression.Literal(value);
- }
-
- // SORT BY /////////////////////////////////////////////////////////////////
-
- /**
- * {@inheritDoc}
- */
- @Override
- public SortBy sort(final String propertyName, final SortOrder order) {
- return new DefaultSortBy(property(propertyName), order);
- }
-
- // CAPABILITIES ////////////////////////////////////////////////////////////
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Operator operator(final String name) {
- return new Capabilities.Operator(name);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public SpatialOperator spatialOperator(final String name, final GeometryOperand[] geometryOperands) {
- return new Capabilities.SpatialOperator(name, geometryOperands);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public FunctionName functionName(final String name, final int nargs) {
- return new Capabilities.FunctionName(name, null, nargs);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public Functions functions(final FunctionName[] functionNames) {
- return new Capabilities.Functions(functionNames);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public SpatialOperators spatialOperators(final SpatialOperator[] spatialOperators) {
- return new Capabilities.SpatialOperators(spatialOperators);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public ComparisonOperators comparisonOperators(final Operator[] comparisonOperators) {
- return new Capabilities.ComparisonOperators(comparisonOperators);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public ArithmeticOperators arithmeticOperators(final boolean simple, final Functions functions) {
- return new Capabilities.ArithmeticOperators(simple, functions);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public ScalarCapabilities scalarCapabilities(final ComparisonOperators comparison,
- final ArithmeticOperators arithmetic, final boolean logical)
- {
- return new Capabilities.ScalarCapabilities(logical, comparison, arithmetic);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public SpatialCapabilities spatialCapabilities(
- final GeometryOperand[] geometryOperands, final SpatialOperators spatial)
- {
- return new Capabilities.SpatialCapabilities(geometryOperands, spatial);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public IdCapabilities idCapabilities(final boolean eid, final boolean fid) {
- return new Capabilities.IdCapabilities(eid, fid);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public FilterCapabilities capabilities(final String version,
- final ScalarCapabilities scalar, final SpatialCapabilities spatial,
- final TemporalCapabilities temporal, final IdCapabilities id)
- {
- return new Capabilities.FilterCapabilities(version, id, spatial, scalar, temporal);
- }
-
- /**
- * {@inheritDoc}
+ * Indicates an property by which contents should be sorted, along with intended order.
+ *
+ * @param property the property to sort by.
+ * @param order the sorting order, ascending or descending.
+ * @return definition of sort order of a property.
*/
@Override
- public TemporalCapabilities temporalCapabilities(TemporalOperand[] temporalOperands, TemporalOperators temporal) {
- return new Capabilities.TemporalCapabilities(temporalOperands, temporal);
+ public SortProperty sort(final ValueReference<? super R, ?> property, final SortOrder order) {
+ return new DefaultSortBy(property, order);
}
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultLike.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultLike.java
index e26c038..f287188 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultLike.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultLike.java
@@ -16,175 +16,223 @@
*/
package org.apache.sis.filter;
+import java.util.List;
import java.util.Arrays;
import java.util.Collection;
import java.util.regex.Pattern;
import org.apache.sis.internal.filter.Node;
import org.apache.sis.util.ArgumentChecks;
-import org.opengis.filter.FilterVisitor;
-import org.opengis.filter.PropertyIsLike;
-import org.opengis.filter.expression.Expression;
+
+// Branch-dependent imports
+import org.opengis.filter.Filter;
+import org.opengis.filter.Expression;
+import org.opengis.filter.LikeOperator;
+
/**
- * The {@value #NAME} filter.
+ * A character string comparison operator with pattern matching.
*
* @author Johann Sorel (Geomatys)
- * @version 2.0
- * @since 2.0
+ * @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
*/
-final class DefaultLike extends Node implements PropertyIsLike {
+final class DefaultLike<R> extends Node implements LikeOperator<R>, Optimization.OnFilter<R> {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 4074943825474742252L;
+
+ /**
+ * The source of values to compare against the pattern.
+ */
+ private final Expression<? super R, ?> expression;
- private final Expression expression;
+ /**
+ * The pattern to match against expression values. The {@link #wildcard}, {@link #singleChar}
+ * and {@link #escape} characters have special meanings.
+ */
private final String pattern;
- private final String wildcard;
- private final String singleChar;
- private final String escape;
- private final boolean matchingCase;
/**
- * Cached java regular expression.
+ * The pattern character for matching any sequence of characters.
+ *
+ * @see #getWildCard()
+ */
+ private final char wildcard;
+
+ /**
+ * The pattern character for matching exactly one character.
+ *
+ * @see #getSingleChar()
*/
- private Pattern regex;
+ private final char singleChar;
- DefaultLike(Expression expression, String pattern, String wildcard, String singleChar, String escape, boolean matchingCase) {
+ /**
+ * The pattern character for indicating that the next character should be matched literally.
+ *
+ * @see #getEscapeChar()
+ */
+ private final char escape;
+
+ /**
+ * Specifies how a filter expression processor should perform string comparisons.
+ *
+ * @see #isMatchingCase()
+ */
+ private final boolean isMatchingCase;
+
+ /**
+ * The regular expression.
+ */
+ private final Pattern regex;
+
+ /**
+ * Creates a new operator.
+ *
+ * @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.
+ */
+ DefaultLike(final Expression<? super R, ?> expression, final String pattern,
+ final char wildcard, final char singleChar, final char escape, final boolean isMatchingCase)
+ {
ArgumentChecks.ensureNonNull("pattern", pattern);
- ArgumentChecks.ensureNonNull("wildcard", wildcard);
- ArgumentChecks.ensureNonNull("singleChar", singleChar);
- ArgumentChecks.ensureNonNull("escape", escape);
- if (wildcard.length() != 1) throw new IllegalArgumentException("WildCard string must be one character long.");
- if (singleChar.length() != 1) throw new IllegalArgumentException("SingleChar string must be one character long.");
- if (escape.length() != 1) throw new IllegalArgumentException("Escape string must be one character long.");
-
- this.expression = expression;
- this.pattern = pattern;
- this.wildcard = wildcard;
- this.singleChar = singleChar;
- this.escape = escape;
- this.matchingCase = matchingCase;
+ this.expression = expression;
+ this.pattern = pattern;
+ this.wildcard = wildcard;
+ this.singleChar = singleChar;
+ this.escape = escape;
+ this.isMatchingCase = isMatchingCase;
+ /*
+ * Creates a regular expression from the given pattern.
+ */
+ final int n = pattern.length();
+ final StringBuilder sb = new StringBuilder(n*2);
+ for (int i=0; i<n; i++) {
+ char c = pattern.charAt(i);
+ if (c == wildcard) {
+ sb.append(".*");
+ continue;
+ } else if (c == singleChar) {
+ sb.append('.');
+ continue;
+ } else if (c == escape) {
+ if (++i >= n) break;
+ c = pattern.charAt(i);
+ }
+ if (isMetaCharacter(c)) {
+ sb.append('\\');
+ }
+ sb.append(c);
+ }
+ int flags = Pattern.CANON_EQ | // Apply canonical decomposition before to match.
+ Pattern.UNICODE_CASE | // Case matching according Unicode standard instead of ASCII.
+ Pattern.UNICODE_CHARACTER_CLASS;
+ if (!isMatchingCase) {
+ flags |= Pattern.CASE_INSENSITIVE;
+ }
+ regex = Pattern.compile(sb.toString(), flags);
}
- @Override
- public String getName() {
- return NAME;
+ /**
+ * Creates a new filter of the same type but different parameters.
+ */
+ private DefaultLike(final DefaultLike<R> original, final Expression<? super R, ?> expression) {
+ this.expression = expression;
+ this.pattern = original.pattern;
+ this.wildcard = original.wildcard;
+ this.singleChar = original.singleChar;
+ this.escape = original.escape;
+ this.isMatchingCase = original.isMatchingCase;
+ this.regex = original.regex;
}
+ /**
+ * Creates a new filter of the same type but different parameters.
+ */
@Override
- protected Collection<?> getChildren() {
- return Arrays.asList(expression);
+ public Filter<R> recreate(final Expression<? super R, ?>[] effective) {
+ return new DefaultLike<>(this, effective[0]);
}
+ /**
+ * Returns the children of this node for displaying purposes.
+ * This is used by {@link #toString()}, {@link #hashCode()} and {@link #equals(Object)} implementations.
+ */
@Override
- public Expression getExpression() {
- return expression;
+ protected Collection<?> getChildren() {
+ // TODO: use List.of(…) in JDK9.
+ return Arrays.asList(expression, pattern);
}
+ /**
+ * Returns the expression whose values will be compared by this operator, together with the pattern.
+ */
@Override
- public String getLiteral() {
- return pattern;
+ public List<Expression<? super R, ?>> getExpressions() {
+ // TODO: use List.of(…) in JDK9.
+ return Arrays.asList(expression, new LeafExpression.Literal<>(pattern));
}
+ /**
+ * Returns the pattern character for matching any sequence of characters.
+ * For the SQL "{@code LIKE}" operator, this property is the {@code %} character.
+ */
@Override
- public String getWildCard() {
+ public char getWildCard() {
return wildcard;
}
+ /**
+ * Returns the pattern character for matching exactly one character.
+ * For the SQL "{@code LIKE}" operator, this property is the {@code _} character.
+ */
@Override
- public String getSingleChar() {
+ public char getSingleChar() {
return singleChar;
}
+ /**
+ * Returns the pattern character for indicating that the next character should be matched literally.
+ * For the SQL "{@code LIKE}" operator, this property is the {@code '} character.
+ */
@Override
- public String getEscape() {
+ public char getEscapeChar() {
return escape;
}
+ /**
+ * Specifies how a filter expression processor should perform string comparisons.
+ */
@Override
public boolean isMatchingCase() {
- return matchingCase;
- }
-
- @Override
- public boolean evaluate(Object object) {
- final String value = expression.evaluate(object, String.class);
- if (value == null) return false;
- return getPattern().matcher(value).matches();
- }
-
- @Override
- public Object accept(FilterVisitor visitor, Object extraData) {
- return visitor.visit(this, extraData);
+ return isMatchingCase;
}
/**
- * Get or create java regex pattern.
+ * Returns {@code true} if the expression value computed from the given object matches the pattern.
*/
- private Pattern getPattern() {
- if (regex == null) {
- //convert pattern to java regex
- final StringBuilder sb = new StringBuilder();
- if (!matchingCase) {
- //add case insensitive
- sb.append("(?i)");
- }
- final int wld = wildcard.charAt(0);
- final int sgl = singleChar.charAt(0);
- final int esc = escape.charAt(0);
-
- for (int i = 0, n = pattern.length(); i < n; i++) {
- char c = pattern.charAt(i);
-
- //user defined special characters
- if (wld == c) {
- sb.append('.');
- sb.append('*');
- } else if (sgl == c) {
- sb.append('.');
- } else if (esc == c) {
- c = pattern.charAt(++i);
- if (c == wld || c == sgl || c == esc) {
- if (isMetaCharacter(c)) {
- sb.append('\\');
- }
- sb.append(c);
- } else {
- throw new IllegalArgumentException("Escape character must be used only to escape wild, single and escape characters.");
- }
- }
- //java regex reserved metacharacters
- else if (isMetaCharacter(c)) {
- sb.append('\\');
- sb.append(c);
- }
- //any other character
- else {
- sb.append(c);
- }
- }
-
- regex = Pattern.compile(sb.toString());
- }
- return regex;
+ @Override
+ public boolean test(final R object) {
+ final Object value = expression.apply(object);
+ return (value != null) && regex.matcher(value.toString()).matches();
}
/**
- * Returns true if given character is a regular expression meta-character.
- */
- private static boolean isMetaCharacter(char c) {
- return c == '.'
- || c == '*'
- || c == '?'
- || c == '('
- || c == ')'
- || c == '['
- || c == ']'
- || c == '{'
- || c == '}'
- || c == '\\'
- || c == '^'
- || c == '$'
- || c == '|'
- || c == '+';
+ * Returns {@code true} if given character is a regular expression meta-character.
+ */
+ static boolean isMetaCharacter(final char c) {
+ return (c == '.' || c == '?' || c == '$') ||
+ (c >= '[' && c <= '^') || // [ \ ] ^
+ (c >= '(' && c <= '+') || // ( ) * +
+ (c >= '{' && c <= '}'); // { | }
}
-
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultObjectId.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultObjectId.java
deleted file mode 100644
index 0bcf605..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultObjectId.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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.io.Serializable;
-import org.apache.sis.util.ArgumentChecks;
-import org.apache.sis.internal.feature.AttributeConvention;
-import org.opengis.feature.Feature;
-import org.opengis.filter.identity.FeatureId;
-import org.opengis.filter.identity.GmlObjectId;
-
-
-/**
- * Default implementation of a few interfaces from the {@link org.opengis.filter.identity} package.
- * Those objects are used for identifying GML objects or other kind of objects.
- *
- * @deprecated the purpose of {@link org.opengis.filter.identity} is questionable.
- * See <a href="https://github.com/opengeospatial/geoapi/issues/32">GeoAPI issue #32</a>.
- *
- * @author Johann Sorel (Geomatys)
- * @version 1.1
- * @since 1.1
- * @module
- */
-@Deprecated
-final class DefaultObjectId implements FeatureId, GmlObjectId, Serializable {
- /**
- * For cross-version compatibility.
- */
- private static final long serialVersionUID = -2877500277700165269L;
-
- /**
- * The identifier.
- */
- private final String identifier;
-
- /**
- * Creates a new identifier.
- */
- DefaultObjectId(final String id) {
- ArgumentChecks.ensureNonNull("id", id);
- identifier = id;
- }
-
- /**
- * Returns the identifier specified at construction time.
- */
- @Override
- public String getID() {
- return identifier;
- }
-
- /**
- * Returns {@code true} if the given object is a feature with the identifier expected by this class.
- */
- @Override
- public boolean matches(final Object feature) {
- if (feature instanceof Feature) {
- Object value = ((Feature) feature).getValueOrFallback(AttributeConvention.IDENTIFIER, null);
- // Feature does not contain the identifier property if null.
- if (value != null) {
- return identifier.equals(String.valueOf(value));
- }
- }
- return false;
- }
-
- /**
- * Returns {@code true} if the given object is an identifier with equals {@link String} code than this object.
- */
- @Override
- public boolean equals(final Object obj) {
- if (obj == this) {
- return true;
- }
- if (obj instanceof DefaultObjectId) {
- return identifier.equals(((DefaultObjectId) obj).identifier);
- }
- return false;
- }
-
- /**
- * Returns a hash code value based on the identifier specified at construction time.
- */
- @Override
- public int hashCode() {
- return identifier.hashCode() ^ (int) serialVersionUID;
- }
-
- /**
- * Returns a string representation of this identifier.
- */
- @Override
- public String toString() {
- return "Id:".concat(identifier);
- }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultSortBy.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultSortBy.java
index 56fc563..b1ebde8 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultSortBy.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultSortBy.java
@@ -17,11 +17,12 @@
package org.apache.sis.filter;
import java.io.Serializable;
+import org.apache.sis.util.ArgumentChecks;
// Branch-dependent imports
-import org.opengis.filter.sort.SortBy;
-import org.opengis.filter.sort.SortOrder;
-import org.opengis.filter.expression.PropertyName;
+import org.opengis.filter.SortOrder;
+import org.opengis.filter.SortProperty;
+import org.opengis.filter.ValueReference;
/**
@@ -32,7 +33,7 @@ import org.opengis.filter.expression.PropertyName;
* @since 1.1
* @module
*/
-final class DefaultSortBy implements SortBy, Serializable {
+final class DefaultSortBy implements SortProperty, Serializable {
/**
* For cross-version compatibility.
*/
@@ -41,7 +42,7 @@ final class DefaultSortBy implements SortBy, Serializable {
/**
* The property on which to apply sorting.
*/
- private final PropertyName property;
+ private final ValueReference<?,?> property;
/**
* The desired order: {@code ASCENDING} or {@code DESCENDING}.
@@ -55,16 +56,18 @@ final class DefaultSortBy implements SortBy, Serializable {
* @param property property on which to apply sorting.
* @param order the desired order: {@code ASCENDING} or {@code DESCENDING}.
*/
- DefaultSortBy(final PropertyName property, final SortOrder order) {
+ DefaultSortBy(final ValueReference<?,?> property, final SortOrder order) {
this.property = property;
this.order = order;
+ ArgumentChecks.ensureNonNull("property", property);
+ ArgumentChecks.ensureNonNull("order", order);
}
/**
* Returns the property to sort by.
*/
@Override
- public PropertyName getPropertyName() {
+ public ValueReference<?,?> getValueReference() {
return property;
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DistanceFilter.java b/core/sis-feature/src/main/java/org/apache/sis/filter/DistanceFilter.java
new file mode 100644
index 0000000..e497ef1
--- /dev/null
+++ b/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>
+ *
+ * @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.
+ * @param <G> the implementation type of geometry objects.
+ *
+ * @since 1.1
+ * @module
+ */
+final class DistanceFilter<R,G> extends BinaryGeometryFilter<R,G> implements DistanceOperator<R> {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = -5304631042699647889L;
+
+ /**
+ * Nature of the operation applied by this {@code DistanceOperator}.
+ */
+ private final DistanceOperatorName operatorType;
+
+ /**
+ * The buffer distance around the geometry of the second expression.
+ */
+ private final Quantity<Length> distance;
+
+ /**
+ * Creates a new spatial function.
+ *
+ * @param operatorType nature of the operation applied by this {@code DistanceOperator}.
+ * @param library the geometry library to use.
+ * @param geometry1 expression fetching the first geometry of the binary operator.
+ * @param geometry2 expression fetching the second geometry of the binary operator.
+ * @param distance the buffer distance around the geometry of the second expression.
+ */
+ DistanceFilter(final DistanceOperatorName operatorType,
+ final Geometries<G> library,
+ final Expression<? super R, ?> geometry1,
+ final Expression<? super R, ?> geometry2,
+ final Quantity<Length> distance)
+ {
+ super(library, geometry1, geometry2, distance.getUnit().getSystemUnit());
+ ArgumentChecks.ensureNonNull("operatorType", operatorType);
+ this.operatorType = operatorType;
+ this.distance = distance;
+ }
+
+ /**
+ * Recreates a new filter of the same type and with the same parameters, but using the given expressions.
+ * This method is invoked when it is possible to simplify or optimize at least one of the expressions that
+ * were given in the original call to the constructor.
+ */
+ @Override
+ protected BinaryGeometryFilter<R,G> recreate(final Expression<? super R, ?> geometry1,
+ final Expression<? super R, ?> geometry2)
+ {
+ return new DistanceFilter<>(operatorType, getGeometryLibrary(expression1), geometry1, geometry2, distance);
+ }
+
+ /**
+ * Identification of this operation.
+ */
+ @Override
+ public DistanceOperatorName getOperatorType() {
+ return operatorType;
+ }
+
+ /**
+ * Returns the two expressions used as parameters by this filter.
+ */
+ @Override
+ public List<Expression<? super R, ?>> getExpressions() {
+ return Arrays.asList(original(expression1), original(expression2), // TODO: use List.of(…) with JDK9.
+ new LeafExpression.Literal<>(distance));
+ }
+
+ /**
+ * Returns the two expressions together with the distance parameter.
+ * This is used for information purpose only, for example in order to build a string representation.
+ */
+ @Override
+ protected Collection<?> getChildren() {
+ return Arrays.asList(original(expression1), original(expression2), distance); // TODO: use List.of(…) with JDK9.
+ }
+
+ /**
+ * Returns the buffer distance around the geometry that will be used when comparing features geometries.
+ */
+ @Override
+ public Quantity<Length> getDistance() {
+ return distance;
+ }
+
+ /**
+ * Returns the literal geometry from which distances are measured.
+ *
+ * @throws IllegalStateException if the geometry is not a literal.
+ */
+ @Override
+ public Geometry getGeometry() {
+ final Literal<? super R, ? extends GeometryWrapper<G>> literal;
+ if (expression2 instanceof Literal<?,?>) {
+ literal = (Literal<? super R, ? extends GeometryWrapper<G>>) expression2;
+ } else if (expression1 instanceof Literal<?,?>) {
+ literal = (Literal<? super R, ? extends GeometryWrapper<G>>) expression1;
+ } else {
+ throw new IllegalStateException();
+ }
+ return literal.getValue();
+ }
+
+ /**
+ * Given an object, determines if the test(s) represented by this filter are passed.
+ *
+ * @param object the object (often a {@link Feature} instance) to evaluate.
+ * @return {@code true} if the test(s) are passed for the provided object.
+ */
+ @Override
+ public boolean test(final R object) {
+ final GeometryWrapper<G> left = expression1.apply(object);
+ if (left != null) {
+ final GeometryWrapper<G> right = expression2.apply(object);
+ if (right != null) try {
+ return left.predicate(operatorType, right, distance, context);
+ } catch (RuntimeException e) {
+ warning(e, true);
+ }
+ }
+ return negativeResult();
+ }
+
+ /**
+ * Returns the value to return when a test can not be applied.
+ */
+ @Override
+ protected boolean negativeResult() {
+ return SpatialOperationContext.negativeResult(operatorType);
+ }
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/FilterByIdentifier.java b/core/sis-feature/src/main/java/org/apache/sis/filter/FilterByIdentifier.java
index 928b60c..d88c8ff 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/FilterByIdentifier.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/FilterByIdentifier.java
@@ -16,21 +16,17 @@
*/
package org.apache.sis.filter;
-import java.util.Map;
-import java.util.Set;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.List;
import java.util.Collection;
import java.util.Collections;
-import org.apache.sis.util.collection.Containers;
-import org.apache.sis.internal.feature.AttributeConvention;
+import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.internal.filter.Node;
+import org.apache.sis.internal.feature.AttributeConvention;
// Branch-dependent imports
import org.opengis.feature.Feature;
-import org.opengis.filter.FilterVisitor;
-import org.opengis.filter.Id;
-import org.opengis.filter.identity.Identifier;
+import org.opengis.filter.Expression;
+import org.opengis.filter.ResourceId;
/**
@@ -38,68 +34,56 @@ import org.opengis.filter.identity.Identifier;
* whose identifier is not in the set.
*
* @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}) used as inputs.
+ *
+ * @since 1.1
* @module
*/
-final class FilterByIdentifier extends Node implements Id {
+final class FilterByIdentifier<R extends Feature> extends Node implements ResourceId<R> {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = 1404452049863376235L;
/**
- * The identifiers of features to retain. Filtering will use the keys. This map contains also
- * the same identifiers as the original {@link Identifier} objects given at construction time,
- * but those values are not used by this class.
+ * The identifier of features to retain.
*/
- private final Map<Object,Identifier> identifiers;
+ private final String identifier;
/**
- * Creates a new filter using the given identifiers.
+ * Creates a new filter using the given identifier.
*/
- FilterByIdentifier(final Collection<? extends Identifier> ids) {
- identifiers = new HashMap<>(Containers.hashMapCapacity(ids.size()));
- for (Identifier id : ids) {
- identifiers.put(id.getID(), id);
- }
+ FilterByIdentifier(final String identifier) {
+ ArgumentChecks.ensureNonEmpty("identifier", identifier);
+ this.identifier = identifier;
}
/**
- * Returns a name identifying this kind of filter.
- */
- @Override
- public String getName() {
- return "Id";
- }
-
- /**
- * Returns the identifiers specified at construction time. This is used for {@link #toString()},
- * {@link #hashCode()} and {@link #equals(Object)} implementations. Since all the keys in the
- * {@link #identifiers} map were derived from the values, comparing those values is sufficient
- * for determining if two {@code FilterByIdentifier} instances are equal.
+ * Returns the identifiers of feature instances to accept.
*/
@Override
- protected Collection<?> getChildren() {
- // Can not return identifiers.values() directly because that collection does not implement equals/hashCode.
- return getIdentifiers();
+ public String getIdentifier() {
+ return identifier;
}
/**
- * Returns the identifiers of feature instances to accept.
+ * Returns the parameters of this filter.
*/
@Override
- public Set<Object> getIDs() {
- return Collections.unmodifiableSet(identifiers.keySet());
+ public List<Expression<? super R, ?>> getExpressions() {
+ return Collections.singletonList(new LeafExpression.Literal<>(identifier));
}
/**
- * Same identifiers than {@link #getIDs()} but as the instances specified at construction time.
- * This is not used by this class.
+ * Returns the identifiers specified at construction time. This is used for {@link #toString()},
+ * {@link #hashCode()} and {@link #equals(Object)} implementations.
*/
@Override
- public Set<Identifier> getIdentifiers() {
- return new HashSet<>(identifiers.values());
+ protected Collection<?> getChildren() {
+ return Collections.singleton(identifier);
}
/**
@@ -107,28 +91,11 @@ final class FilterByIdentifier extends Node implements Id {
* is one of the identifier specified at {@code FilterByIdentifier} construction time.
*/
@Override
- public boolean evaluate(Object object) {
- if (object instanceof Feature) {
- Object id = ((Feature) object).getValueOrFallback(AttributeConvention.IDENTIFIER, null);
- if (identifiers.containsKey(id)) {
- return true;
- }
- if (id != null && !(id instanceof String)) {
- /*
- * Sometime web services specify the identifiers to use for filtering as Strings
- * while the types stored in the feature instances is different.
- */
- return identifiers.containsKey(id.toString());
- }
+ public boolean test(R object) {
+ if (object == null) {
+ return false;
}
- return false;
- }
-
- /**
- * Implementation of the visitor pattern.
- */
- @Override
- public Object accept(FilterVisitor visitor, Object extraData) {
- return visitor.visit(this, extraData);
+ final Object id = object.getValueOrFallback(AttributeConvention.IDENTIFIER, null);
+ return (id != null) && identifier.equals(id.toString());
}
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/InvalidExpressionException.java b/core/sis-feature/src/main/java/org/apache/sis/filter/InvalidExpressionException.java
deleted file mode 100644
index 2a3ba68..0000000
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/InvalidExpressionException.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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 org.opengis.filter.expression.Expression;
-import org.apache.sis.internal.feature.Resources;
-import org.apache.sis.internal.filter.Node;
-import org.apache.sis.util.Classes;
-import org.apache.sis.util.Workaround;
-
-
-/**
- * Thrown when an operation can not complete because an expression is illegal or unsupported.
- * The invalid {@link Expression} may be a component of a larger object such as another expression
- * or a query {@link org.apache.sis.storage.Query}.
- *
- * @author Martin Desruisseaux (Geomatys)
- * @version 1.1
- * @since 1.1
- * @module
- */
-public class InvalidExpressionException extends RuntimeException {
- /**
- * Serial number for inter-operability with different versions.
- */
- private static final long serialVersionUID = -2600709421042246855L;
-
- /**
- * Constructs an exception with no detail message.
- */
- public InvalidExpressionException() {
- }
-
- /**
- * Constructs an exception with the specified detail message.
- *
- * @param message the detail message.
- */
- public InvalidExpressionException(final String message) {
- super(message);
- }
-
- /**
- * Constructs an exception with the specified detail message and cause.
- *
- * @param message the detail message.
- * @param cause the cause for this exception.
- */
- public InvalidExpressionException(final String message, final Throwable cause) {
- super(message, cause);
- }
-
- /**
- * Constructs an exception with a message saying that the given expression is illegal or unsupported.
- * This constructor assumes that the expression was part of a larger object containing many expressions
- * identified by indices.
- *
- * @param expression the illegal expression, or {@code null} if unknown.
- * @param index column number (or other kind of index) where the invalid expression has been found.
- */
- public InvalidExpressionException(final Expression expression, final int index) {
- super(message(expression, index));
- }
-
- /**
- * Work around for RFE #4093999 in Sun's bug database
- * ("Relax constraint on placement of this()/super() call in constructors").
- */
- @Workaround(library="JDK", version="1.8")
- private static String message(final Expression expression, final int index) {
- final String name;
- if (expression instanceof Node) {
- name = ((Node) expression).getName();
- } else {
- name = Classes.getShortClassName(expression);
- }
- return Resources.format(Resources.Keys.InvalidExpression_2, name, index);
- }
-}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java b/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
index ef2affd..900c892 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/LeafExpression.java
@@ -16,12 +16,12 @@
*/
package org.apache.sis.filter;
-import java.util.Map;
+import java.util.List;
import java.util.Collection;
import java.util.Collections;
import org.apache.sis.util.Classes;
import org.apache.sis.util.iso.Names;
-import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.ObjectConverters;
import org.apache.sis.util.UnconvertibleObjectException;
import org.apache.sis.util.collection.WeakValueHashMap;
@@ -31,15 +31,9 @@ import org.apache.sis.feature.builder.FeatureTypeBuilder;
import org.apache.sis.feature.builder.PropertyTypeBuilder;
// Branch-dependent imports
-import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
-import org.opengis.feature.PropertyType;
import org.opengis.feature.AttributeType;
-import org.opengis.feature.IdentifiedType;
-import org.opengis.feature.Operation;
-import org.opengis.feature.PropertyNotFoundException;
-import org.opengis.filter.expression.Expression;
-import org.opengis.filter.expression.ExpressionVisitor;
+import org.opengis.filter.Expression;
/**
@@ -49,10 +43,14 @@ import org.opengis.filter.expression.ExpressionVisitor;
* @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}) used as inputs.
+ * @param <V> the type of value computed by the expression.
+ *
+ * @since 1.1
* @module
*/
-abstract class LeafExpression extends Node implements Expression, FeatureExpression {
+abstract class LeafExpression<R,V> extends Node implements FeatureExpression<R,V> {
/**
* For cross-version compatibility.
*/
@@ -65,115 +63,12 @@ abstract class LeafExpression extends Node implements Expression, FeatureExpress
}
/**
- * Evaluates the expression for producing a result of the given type.
- * If this method can not produce a value of the given type, then it returns {@code null}.
- * This implementation evaluates the expression {@linkplain #evaluate(Object) in the default way},
- * then tries to convert the result to the target type.
- *
- * @param feature to feature to evaluate with this expression.
- * @param target the desired type for the expression result.
- * @return the result, or {@code null} if it can not be of the specified type.
+ * Returns the expression used as parameters for this function,
+ * which is an empty list.
*/
@Override
- public final <T> T evaluate(final Object feature, final Class<T> target) {
- ArgumentChecks.ensureNonNull("target", target);
- final Object value = evaluate(feature);
- try {
- return ObjectConverters.convert(value, target);
- } catch (UnconvertibleObjectException e) {
- warning(e);
- return null; // As per method contract.
- }
- }
-
-
-
-
- /**
- * Expression whose value is computed by retrieving the value indicated by the provided name.
- * A property name does not store any value; it acts as an indirection to a property value of
- * the evaluated feature.
- */
- static final class Property extends LeafExpression implements org.opengis.filter.expression.PropertyName {
- /** For cross-version compatibility. */
- private static final long serialVersionUID = 3417789380239058201L;
-
- /** Name of the property from which to retrieve the value. */
- private final String name;
-
- /** Creates a new expression retrieving values from a property of the given name. */
- Property(final String name) {
- ArgumentChecks.ensureNonNull("name", name);
- this.name = name;
- }
-
- /** Identification of this expression. */
- @Override public String getName() {
- return "PropertyName";
- }
-
- /** For {@link #toString()}, {@link #hashCode()} and {@link #equals(Object)} implementations. */
- @Override protected Collection<?> getChildren() {
- return Collections.singleton(name);
- }
-
- /** Returns the name of the property whose value will be returned by the {@link #evaluate(Object)} method. */
- @Override public String getPropertyName() {
- return name;
- }
-
- /**
- * Returns the value of the property of the given name.
- * The {@code candidate} object can be any of the following type:
- *
- * <ul>
- * <li>A {@link Feature}, in which case {@link Feature#getPropertyValue(String)} will be invoked.</li>
- * <li>A {@link Map}, in which case {@link Map#get(Object)} will be invoked.</li>
- * </ul>
- *
- * If no value is found for the given feature, then this method returns {@code null}.
- */
- @Override
- public Object evaluate(final Object candidate) {
- if (candidate instanceof Feature) try {
- return ((Feature) candidate).getPropertyValue(name);
- } catch (PropertyNotFoundException ex) {
- warning(ex);
- // Null will be returned below.
- } else if (candidate instanceof Map<?,?>) {
- return ((Map<?,?>) candidate).get(name);
- }
- return null;
- }
-
- /**
- * Provides the expected type of values produced by this expression when a feature of the given type is evaluated.
- *
- * @param valueType the type of features to be evaluated by the given expression.
- * @param addTo where to add the type of properties evaluated by the given expression.
- * @return builder of the added property, or {@code null} if this method can not add a property.
- * @throws IllegalArgumentException if this method can not determine the property type for the given feature type.
- */
- @Override
- public PropertyTypeBuilder expectedType(final FeatureType valueType, final FeatureTypeBuilder addTo) {
- PropertyType type = valueType.getProperty(name); // May throw IllegalArgumentException.
- while (type instanceof Operation) {
- final IdentifiedType result = ((Operation) type).getResult();
- if (result != type && result instanceof PropertyType) {
- type = (PropertyType) result;
- } else if (result instanceof FeatureType) {
- return addTo.addAssociation((FeatureType) result).setName(name);
- } else {
- return null;
- }
- }
- return addTo.addProperty(type);
- }
-
- /** Implementation of the visitor pattern. */
- @Override public Object accept(final ExpressionVisitor visitor, final Object extraData) {
- return visitor.visit(this, extraData);
- }
+ public final List<Expression<? super R, ?>> getParameters() {
+ return Collections.emptyList();
}
@@ -181,24 +76,18 @@ abstract class LeafExpression extends Node implements Expression, FeatureExpress
/**
* A constant, literal value that can be used in expressions.
- * The {@link #evaluate(Object)} method ignores the argument and always returns {@link #getValue()}.
+ * The {@link #apply(Object)} method ignores the argument and always returns {@link #getValue()}.
*/
- static final class Literal extends LeafExpression implements org.opengis.filter.expression.Literal {
+ static class Literal<R,V> extends LeafExpression<R,V> implements org.opengis.filter.Literal<R,V> {
/** For cross-version compatibility. */
private static final long serialVersionUID = -8383113218490957822L;
/** The constant value to be returned by {@link #getValue()}. */
- private final Object value;
+ private final V value;
/** Creates a new literal holding the given constant value. */
- Literal(final Object value) {
- ArgumentChecks.ensureNonNull("value", value);
- this.value = value;
- }
-
- /** Identification of this expression. */
- @Override public String getName() {
- return "Literal";
+ Literal(final V value) {
+ this.value = value; // Null is accepted.
}
/** For {@link #toString()}, {@link #hashCode()} and {@link #equals(Object)} implementations. */
@@ -207,17 +96,39 @@ abstract class LeafExpression extends Node implements Expression, FeatureExpress
}
/** Returns the constant value held by this object. */
- @Override public Object getValue() {
+ @Override public V getValue() {
return value;
}
+ /** Returns the type of values computed by this expression. */
+ @Override public Class<?> getValueClass() {
+ return (value != null) ? value.getClass() : Object.class;
+ }
+
/** Expression evaluation, which just returns the constant value. */
- @Override public Object evaluate(Object ignored) {
+ @Override public V apply(Object ignored) {
return value;
}
/**
- * Provides the type of values returned by {@link #evaluate(Object)}
+ * Returns an expression that provides values as instances of the specified class.
+ *
+ * @throws ClassCastException if values can not be provided as instances of the specified class.
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public <N> Expression<R,N> toValueType(final Class<N> type) {
+ try {
+ final N c = ObjectConverters.convert(value, type);
+ return (c != value) ? new Literal<>(c) : (Literal<R,N>) this;
+ } catch (UnconvertibleObjectException e) {
+ throw (ClassCastException) new ClassCastException(Errors.format(
+ Errors.Keys.CanNotConvertValue_2, getFunctionName(), type)).initCause(e);
+ }
+ }
+
+ /**
+ * Provides the type of values returned by {@link #apply(Object)}
* wrapped in an {@link AttributeType} named "Literal".
*
* @param addTo where to add the type of properties evaluated by the given expression.
@@ -225,7 +136,7 @@ abstract class LeafExpression extends Node implements Expression, FeatureExpress
*/
@Override
public PropertyTypeBuilder expectedType(FeatureType ignored, final FeatureTypeBuilder addTo) {
- final Class<?> valueType = value.getClass();
+ final Class<?> valueType = getValueClass();
AttributeType<?> propertyType = TYPES.get(valueType);
if (propertyType == null) {
final Class<?> standardType = Classes.getStandardType(valueType);
@@ -248,13 +159,38 @@ abstract class LeafExpression extends Node implements Expression, FeatureExpress
* 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"));
}
+ }
+
+
+
+
+ /**
+ * A literal value which is the result of transforming an other literal.
+ */
+ static final class Transformed<R,V> extends Literal<R,V> implements Optimization.OnExpression<R,V> {
+ /** For cross-version compatibility. */
+ private static final long serialVersionUID = -5120203649333919221L;
+
+ /** The original expression. */
+ final Expression<R,?> original;
+
+ /** Creates a new literal holding the given constant value. */
+ Transformed(final V value, final Expression<R,?> original) {
+ super(value);
+ this.original = original;
+ }
- /** Implementation of the visitor pattern. */
- @Override public Object accept(final ExpressionVisitor visitor, final Object extraData) {
- return visitor.visit(this, extraData);
+ /**
+ * Returns the same literal without the reference to the original expression.
+ * Since this {@code Transformed} instance will not longer be unwrapped,
+ * the transformed value will become visible to users.
+ */
+ @Override
+ public Expression<? super R, ? extends V> optimize(final Optimization optimization) {
+ return new Literal<>(getValue());
}
}
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFunction.java
index cbd19a7..5418016 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/LogicalFunction.java
@@ -16,28 +16,36 @@
*/
package org.apache.sis.filter;
-import java.util.Arrays;
import java.util.List;
import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.internal.filter.Node;
+import org.apache.sis.internal.util.CollectionsExt;
import org.apache.sis.internal.util.UnmodifiableArrayList;
+import org.apache.sis.util.resources.Errors;
// Branch-dependent imports
import org.opengis.filter.Filter;
-import org.opengis.filter.FilterVisitor;
+import org.opengis.filter.LogicalOperator;
+import org.opengis.filter.LogicalOperatorName;
/**
- * Logical filter (AND, OR) using an arbitrary number of filters.
+ * Logical filter (AND, OR) using an arbitrary number of operands.
+ * Operands are other filters.
*
* @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}) used as inputs.
+ *
+ * @since 1.1
* @module
*/
-abstract class LogicalFunction extends Node {
+abstract class LogicalFunction<R> extends Node implements LogicalOperator<R>, Optimization.OnFilter<R> {
/**
* For cross-version compatibility.
*/
@@ -46,116 +54,247 @@ abstract class LogicalFunction extends Node {
/**
* The filter on which to apply the logical operator.
*/
- protected final Filter[] filters;
+ protected final Filter<? super R>[] operands;
/**
- * Creates a new logical function applied on the given filters.
+ * Creates a new logical operator applied on the given operands.
+ *
+ * @param op operands of the new operator.
*/
- protected LogicalFunction(final Collection<? extends Filter> f) {
- ArgumentChecks.ensureNonNull("filters", f);
- filters = f.toArray(new Filter[f.size()]);
- for (int i=0; i<filters.length; i++) {
- ArgumentChecks.ensureNonNullElement("filters", i, filters[i]);
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ LogicalFunction(final Collection<? extends Filter<? super R>> op) {
+ ArgumentChecks.ensureNonEmpty("operands", op);
+ operands = op.toArray(new Filter[op.size()]);
+ if (operands.length < 2) {
+ throw new IllegalArgumentException(Errors.format(Errors.Keys.TooFewArguments_2, 2, operands.length));
+ }
+ for (int i=0; i<operands.length; i++) {
+ ArgumentChecks.ensureNonNullElement("operands", i, operands[i]);
}
- ArgumentChecks.ensureSizeBetween("filters", 2, Integer.MAX_VALUE, filters.length);
}
/**
- * Returns a list containing all of the child filters of this object.
- * This list will contain at least two elements.
+ * Creates a new logical operator of the same kind than this operator.
+ *
+ * @param op operands of the new operator.
+ * @return the new operator.
*/
- @Override
- public final List<Filter> getChildren() {
- return UnmodifiableArrayList.wrap(filters);
- }
+ protected abstract LogicalFunction<R> createSameType(Collection<? extends Filter<? super R>> op);
/**
- * Returns a hash code value for this filter.
+ * Returns a list containing all of the child filters of this object.
*/
@Override
- public final int hashCode() {
- return getClass().hashCode() ^ Arrays.hashCode(filters);
+ public final List<Filter<? super R>> getOperands() {
+ return UnmodifiableArrayList.wrap(operands);
}
/**
- * Compares this filter with the given object for equality.
+ * Returns the children of this node for displaying purposes.
+ * This is used by {@link #toString()}, {@link #hashCode()} and {@link #equals(Object)} implementations.
*/
@Override
- public final boolean equals(final Object obj) {
- if (obj == this) {
- return true;
- }
- if (obj != null && obj.getClass() == getClass()) {
- return Arrays.equals(filters, ((LogicalFunction) obj).filters);
- }
- return false;
+ protected final Collection<?> getChildren() {
+ return getOperands();
}
/**
* The "And" operation (⋀).
- *
- * @see org.apache.sis.filter.ComparisonFunction.Between
*/
- static final class And extends LogicalFunction implements org.opengis.filter.And {
+ static final class And<R> extends LogicalFunction<R> {
/** For cross-version compatibility. */
private static final long serialVersionUID = 152892064260384713L;
- /** Creates a new expression for the given filters. */
- And(final Collection<? extends Filter> filters) {
- super(filters);
+ /** Creates a new operator for the given operands. */
+ And(final Collection<? extends Filter<? super R>> op) {
+ super(op);
}
- /** Returns a name for this filter. */
- @Override public String getName() {return "And";}
- @Override protected char symbol() {return filters.length <= 2 ? '∧' : '⋀';}
+ /** Creates a new logical operator of the same kind than this operator. */
+ @Override protected LogicalFunction<R> createSameType(Collection<? extends Filter<? super R>> op) {
+ return new And<>(op);
+ }
- /** Implementation of the visitor pattern. */
- @Override public Object accept(FilterVisitor visitor, Object extraData) {
- return visitor.visit(this, extraData);
+ /** Identification of the operation. */
+ @Override public LogicalOperatorName getOperatorType() {
+ return LogicalOperatorName.AND;
+ }
+
+ /** Symbol of the operation. */
+ @Override protected char symbol() {
+ return operands.length <= 2 ? '∧' : '⋀';
}
/** Executes the logical operation. */
- @Override public boolean evaluate(final Object object) {
- for (final Filter filter : filters) {
- if (!filter.evaluate(object)) {
+ @Override public boolean test(final R object) {
+ for (final Filter<? super R> filter : operands) {
+ if (!filter.test(object)) {
return false;
}
}
return true;
}
+
+ /** Tries to optimize this filter. */
+ @Override public Filter<? super R> optimize(final Optimization optimization) {
+ return optimize(optimization, Filter.include(), Filter.exclude());
+ }
}
/**
* The "Or" operation (⋁).
*/
- static final class Or extends LogicalFunction implements org.opengis.filter.Or {
+ static final class Or<R> extends LogicalFunction<R> {
/** For cross-version compatibility. */
private static final long serialVersionUID = 3805785720811330282L;
- /** Creates a new expression for the given filters. */
- Or(final Collection<? extends Filter> filters) {
- super(filters);
+ /** Creates a new operator for the given operands. */
+ Or(final Collection<? extends Filter<? super R>> op) {
+ super(op);
}
- /** Returns a name for this filter. */
- @Override public String getName() {return "Or";}
- @Override protected char symbol() {return filters.length <= 2 ? '∨' : '⋁';}
+ /** Creates a new logical operator of the same kind than this operator. */
+ @Override protected LogicalFunction<R> createSameType(Collection<? extends Filter<? super R>> op) {
+ return new Or<>(op);
+ }
+
+ /** Identification of the operation. */
+ @Override public LogicalOperatorName getOperatorType() {
+ return LogicalOperatorName.OR;
+ }
- /** Implementation of the visitor pattern. */
- @Override public Object accept(FilterVisitor visitor, Object extraData) {
- return visitor.visit(this, extraData);
+ /** Symbol of the operation. */
+ @Override protected char symbol() {
+ return operands.length <= 2 ? '∨' : '⋁';
}
/** Executes the logical operation. */
- @Override public boolean evaluate(final Object object) {
- for (Filter filter : filters) {
- if (filter.evaluate(object)) {
+ @Override public boolean test(final R object) {
+ for (Filter<? super R> filter : operands) {
+ if (filter.test(object)) {
return true;
}
}
return false;
}
+
+ /** Tries to optimize this filter. */
+ @Override public Filter<? super R> optimize(final Optimization optimization) {
+ return optimize(optimization, Filter.exclude(), Filter.include());
+ }
+ }
+
+
+ /**
+ * The negation filter (¬).
+ */
+ static final class Not<R> extends Node implements LogicalOperator<R>, Optimization.OnFilter<R> {
+ /** For cross-version compatibility. */
+ private static final long serialVersionUID = -1296823195138427781L;
+
+ /** The filter to negate. */
+ private final Filter<? super R> operand;
+
+ /** Creates a new operator. */
+ Not(final Filter<? super R> operand) {
+ ArgumentChecks.ensureNonNull("operand", operand);
+ this.operand = operand;
+ }
+
+ /** Identification of the operation. */
+ @Override public LogicalOperatorName getOperatorType() {
+ return LogicalOperatorName.NOT;
+ }
+
+ /** Symbol of the operation. */
+ @Override protected char symbol() {
+ return '¬';
+ }
+
+ /** Returns the singleton expression tested by this operator. */
+ @Override protected final Collection<?> getChildren() {
+ return getOperands();
+ }
+
+ /** Returns the singleton filter used by this operation. */
+ @Override public List<Filter<? super R>> getOperands() {
+ return Collections.singletonList(operand);
+ }
+
+ /** Evaluates this filter on the given object. */
+ @Override public boolean test(final R object) {
+ return !operand.test(object);
+ }
+
+ /** Tries to optimize this filter. */
+ @Override public Filter<? super R> optimize(final Optimization optimization) {
+ final Filter<? super R> effective = optimization.apply(operand);
+ if (effective == Filter.include()) return Filter.exclude();
+ if (effective == Filter.exclude()) return Filter.include();
+ if (effective instanceof Not<?>) {
+ return ((Not<? super R>) effective).operand; // NOT(NOT(C)) == C
+ } else {
+ /*
+ * TODO:
+ * NOT(EQUALS(A,B)) = NOT_EQUALS(A,B)
+ * NOT(NOT_EQUALS(A,B)) = EQUALS(A,B)
+ */
+ }
+ return (effective != operand) ? new Not<>(effective) : this;
+ }
+ }
+
+ /**
+ * Returns the filter to use with duplicated operands removed,
+ * nested operations of the same type inlined, and literal values processed immediately.
+ * If no simplification has been applied, then this method returns {@code this}.
+ *
+ * @param optimization the simplifications or optimizations to apply on this filter.
+ * @param ignore the filter to ignore (literal "true" or "false").
+ * @param shortCircuit the filter to use if found (literal "true" or "false").
+ */
+ final Filter<? super R> optimize(final Optimization optimization,
+ final Filter<R> ignore, final Filter<R> shortCircuit)
+ {
+ boolean unchanged = true; // Will be `false` if at least one simplification has been applied.
+ final Class<?> inline = getClass(); // Filter class for which to expand operands in the optimized filter.
+ final Collection<Filter<? super R>> effective = new LinkedHashSet<>();
+ for (Filter<? super R> f : operands) {
+ unchanged &= (f == (f = optimization.apply(f)));
+ if (f == ignore) {
+ unchanged = false;
+ } else if (f == shortCircuit) {
+ return shortCircuit;
+ } else if (f.getClass() != inline) {
+ unchanged &= effective.add(f);
+ } else {
+ unchanged = false;
+ for (Filter<? super R> s : ((LogicalFunction<? super R>) f).operands) {
+ if (f != ignore) {
+ if (f == shortCircuit) {
+ return shortCircuit;
+ } else {
+ // No need to check for the type because `f` has already been inlined.
+ assert s.getClass() != inline;
+ effective.add(optimization.apply(s));
+ }
+ }
+ }
+ }
+ }
+ /*
+ * TODO:
+ * - Replace BETWEEN(A,B,C) by A >= B and A <= C in order to allow optimizations below.
+ * - Replace A >= B && A >= C by A >= MAX(B,C) with max evaluated immediately if possible.
+ * - Same for MIN.
+ * - Restore A >= B && A >= C as BETWEEN(A,B,C) where B or C may be MAX/MIN.
+ */
+ if (unchanged) {
+ return this;
+ }
+ final Filter<? super R> c = CollectionsExt.singletonOrNull(effective);
+ return (c != null) ? c : createSameType(effective);
}
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java b/core/sis-feature/src/main/java/org/apache/sis/filter/Optimization.java
new file mode 100644
index 0000000..cf48f80
--- /dev/null
+++ b/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;
... 24451 lines suppressed ...