This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/sis.git
commit f034daa245c647657ce75dfeea65ce131b99a1e6 Merge: e336ffb266 af22957697 Author: Martin Desruisseaux <[email protected]> AuthorDate: Mon Jan 26 22:38:32 2026 +0100 Merge branch 'geoapi-3.1' .../org/apache/sis/filter/ComparisonFilter.java | 455 ++++------- .../main/org/apache/sis/filter/Optimization.java | 116 ++- .../main/org/apache/sis/filter/TemporalFilter.java | 26 +- .../org/apache/sis/filter/TemporalOperation.java | 131 ++-- .../sis/filter/base/BinaryFunctionWidening.java | 3 +- .../apache/sis/filter/base/ConvertFunction.java | 12 +- .../main/org/apache/sis/filter/base/Node.java | 24 +- .../apache/sis/filter/math/ArithmeticFunction.java | 79 +- .../org/apache/sis/filter/math/BinaryOperator.java | 9 +- .../main/org/apache/sis/filter/math/Function.java | 6 + .../main/org/apache/sis/filter/math/Predicate.java | 3 +- .../org/apache/sis/filter/math/UnaryOperator.java | 3 +- .../filter/{ => math}/ArithmeticFunctionTest.java | 5 +- .../org/apache/sis/temporal/DefaultInstant.java | 14 +- .../main/org/apache/sis/temporal/TimeMethods.java | 843 ++++++++++++++++----- .../operation/transform/PassThroughTransform.java | 10 +- .../operation/transform/TransformJoiner.java | 250 +++++- .../operation/transform/WraparoundTransform.java | 25 +- .../transform/WraparoundTransformTest.java | 72 ++ .../apache/sis/storage/base/MetadataBuilder.java | 4 +- .../org/apache/sis/storage/FeatureQueryTest.java | 41 +- .../main/org/apache/sis/converter/ClassPair.java | 2 +- .../apache/sis/converter/ConverterRegistry.java | 2 +- .../org/apache/sis/converter/DateConverter.java | 14 +- .../org/apache/sis/converter/StringConverter.java | 1 + .../org/apache/sis/converter/SystemRegistry.java | 2 +- .../main/org/apache/sis/system/Loggers.java | 3 +- .../sis/referencing/factory/sql/epsg/README.md | 24 +- 28 files changed, 1481 insertions(+), 698 deletions(-) diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java index b79c097a88,4025972525..28bd310aa7 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java @@@ -18,32 -18,24 +18,21 @@@ package org.apache.sis.filter import java.math.BigDecimal; import java.math.BigInteger; + import java.time.DateTimeException; import java.util.List; import java.util.Collection; - import java.util.Date; - import java.util.Calendar; import java.util.Objects; - import java.time.Instant; - import java.time.LocalTime; - import java.time.OffsetTime; - import java.time.LocalDateTime; - import java.time.OffsetDateTime; - import java.time.ZonedDateTime; - import java.time.ZoneId; - import java.time.chrono.ChronoLocalDate; - import java.time.chrono.ChronoLocalDateTime; - import java.time.chrono.ChronoZonedDateTime; - import java.time.temporal.ChronoField; - import java.time.temporal.Temporal; + import java.util.function.BiPredicate; -import org.opengis.util.CodeList; import org.apache.sis.math.Fraction; import org.apache.sis.filter.base.Node; import org.apache.sis.filter.base.BinaryFunctionWidening; + import org.apache.sis.temporal.TimeMethods; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.filter.Filter; -import org.opengis.filter.Expression; -import org.opengis.filter.MatchAction; -import org.opengis.filter.ComparisonOperatorName; -import org.opengis.filter.BinaryComparisonOperator; -import org.opengis.filter.BetweenComparisonOperator; +// Specific to the main branch: +import org.apache.sis.pending.geoapi.filter.MatchAction; +import org.apache.sis.pending.geoapi.filter.ComparisonOperatorName; +import org.apache.sis.pending.geoapi.filter.BinaryComparisonOperator; +import org.apache.sis.pending.geoapi.filter.BetweenComparisonOperator; /** @@@ -162,6 -154,118 +151,118 @@@ abstract class ComparisonFilter<R> exte return false; } + /** + * Whether to convert literals to the same type as non-literal parameters during the optimization phase. + * This is invoked by {@link Optimization} for deciding whether to attempt such replacement. + * + * @return whether it is okay to convert literals in advance. + */ + @Override + public final boolean allowLiteralConversions() { + return true; + } + + /** + * Tries to optimize this filter. Fist, this method applies the optimization documented + * in the {@linkplain Optimization.OnFilter#optimize default method impmementation}. + * Then, if it is possible to avoid to inspect the number types every time that the + * filter is evaluated, this method returns a more direct implementation. + * + * @param optimization the simplifications or optimizations to apply on this filter. + * @return the simplified or optimized filter, or {@code this} if no optimization has been applied. + */ + @Override + public final Filter<R> optimize(final Optimization optimization) { + final Filter<R> result = Optimization.OnFilter.super.optimize(optimization); + if (result instanceof ComparisonFilter<?>) { + final var optimized = (ComparisonFilter<R>) result; + final Class<?> t1, t2; + if (isSpecialized(t1 = getResultClass(expression1)) && + isSpecialized(t2 = getResultClass(expression2))) + { + final var numeric = optimized.new Numeric(); + if (numeric.evaluator != null) { + return numeric; + } + final var temporal = new Time<>(TimeMethods.forTypes(t1, t2), t2); + if (temporal.evaluator != null) { + return temporal; + } + } + } + return result; + } + + /** + * Returns whether the given type is non-null and something more specialized than {@code Object}. + * This is used for avoiding unnecessary class-loading of {@link Numeric} and {@link Time} when + * they are sure to be unsuccessful. + */ + private static boolean isSpecialized(final Class<?> type) { + return (type != null) && (type != Object.class); + } + + /** + * An optimized versions of this filter for the case where the operands are numeric. + */ + private final class Numeric extends Node implements Filter<R> { + /** For cross-version compatibility during (de)serialization. */ + private static final long serialVersionUID = 4969425622445580192L; + + /** The expression which performs the comparison and returns the result as an integer. */ + @SuppressWarnings("serial") final Expression<R, ? extends Number> evaluator; + + /** Creates a new filter. Callers must verifies that {@link #evaluator} is non-null. */ + Numeric() {evaluator = specialize();} + + /** Delegates to the enclosing class.*/ - @Override public CodeList<?> getOperatorType() {return ComparisonFilter.this.getOperatorType();} ++ @Override public Enum<?> getOperatorType() {return ComparisonFilter.this.getOperatorType();} + @Override public Class<? super R> getResourceClass() {return ComparisonFilter.this.getResourceClass();} + @Override public List<Expression<R,?>> getExpressions() {return ComparisonFilter.this.getExpressions();} + @Override protected Collection<?> getChildren() {return ComparisonFilter.this.getChildren();} + + /** Determines if the test represented by this filter passes with the given operands. */ + @Override public boolean test(final R candidate) { + return ((Integer) evaluator.apply(candidate)) != 0; + } + } + + /** + * An optimized versions of this filter for the case where the operands are temporal. + */ + private final class Time<T,S> extends Node implements Filter<R> { + /** For cross-version compatibility during (de)serialization. */ + private static final long serialVersionUID = -5132906457258846016L; + + /** The function which performs the comparisons. */ + @SuppressWarnings("serial") final BiPredicate<T,S> evaluator; + + /** Creates a new filter. Callers must verifies that {@link #evaluator} is non-null. */ + Time(final TimeMethods<T> methods, final Class<S> otherType) { + evaluator = (methods != null) ? methods.predicate(temporalTest(), otherType) : null; + } + + /** Delegates to the enclosing class.*/ - @Override public CodeList<?> getOperatorType() {return ComparisonFilter.this.getOperatorType();} ++ @Override public Enum<?> getOperatorType() {return ComparisonFilter.this.getOperatorType();} + @Override public Class<? super R> getResourceClass() {return ComparisonFilter.this.getResourceClass();} + @Override public List<Expression<R,?>> getExpressions() {return ComparisonFilter.this.getExpressions();} + @Override protected Collection<?> getChildren() {return ComparisonFilter.this.getChildren();} + + /** Determines if the test represented by this filter passes with the given operands. */ + @Override public boolean test(final R candidate) { + @SuppressWarnings("unchecked") + final T left = (T) expression1.apply(candidate); + if (left != null) { + @SuppressWarnings("unchecked") + final S right = (S) expression2.apply(candidate); + if (right != null) { + return evaluator.test(left, right); + } + } + return false; + } + } + /** * Determines if the test(s) represented by this filter passes with the given operands. * Values of {@link #expression1} and {@link #expression2} can be two single values, diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalOperation.java index 57962fa707,ff6fa3905a..66f6956580 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalOperation.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalOperation.java @@@ -23,15 -23,12 +23,12 @@@ import java.time.temporal.Temporal import org.apache.sis.util.internal.shared.Strings; import org.apache.sis.util.collection.WeakHashSet; import org.apache.sis.temporal.TimeMethods; - import static org.apache.sis.temporal.TimeMethods.BEFORE; - import static org.apache.sis.temporal.TimeMethods.AFTER; - import static org.apache.sis.temporal.TimeMethods.EQUAL; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.temporal.Period; -import org.opengis.temporal.Instant; -import org.opengis.temporal.IndeterminateValue; -import org.opengis.filter.TemporalOperatorName; +// Specific to the main branch: +import org.apache.sis.pending.geoapi.temporal.Period; +import org.apache.sis.pending.geoapi.temporal.Instant; +import org.apache.sis.pending.geoapi.temporal.IndeterminateValue; +import org.apache.sis.pending.geoapi.temporal.TemporalOperatorName; /** diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/ArithmeticFunction.java index 3e355f571d,469d03b35c..1df4b0c251 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/ArithmeticFunction.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/ArithmeticFunction.java @@@ -94,7 -93,7 +93,7 @@@ public abstract class ArithmeticFunctio * @param name name of the attribute to create. * @return an attribute of the given name for numbers. */ - private static DefaultAttributeType<Number> createNumericType(final String name) { - private static AttributeType<Number> createNumericType(final ScopedName name) { ++ private static DefaultAttributeType<Number> createNumericType(final ScopedName name) { return createType(Number.class, name); } @@@ -199,14 -197,11 +197,11 @@@ effective[1].toValueType(Number.class)); } - /** Description of results of the {@code "Add"} expression. */ + private static final ScopedName NAME = createName(FunctionNames.Add); - private static final AttributeType<Number> TYPE = createNumericType(NAME); - @Override protected AttributeType<Number> expectedType() {return TYPE;} ++ private static final DefaultAttributeType<Number> TYPE = createNumericType(NAME); + @Override protected DefaultAttributeType<Number> expectedType() {return TYPE;} - private static final DefaultAttributeType<Number> TYPE = createNumericType(FunctionNames.Add); - - /** Representation of the {@code "Add"} operation. */ - @Override protected char symbol() {return '+';} @Override public ScopedName getFunctionName() {return NAME;} - private static final ScopedName NAME = createName(FunctionNames.Add); + @Override protected char symbol() {return '+';} /** Applies this expression to the given operands. */ @Override protected Number applyAsDouble (double left, double right) {return left + right;} @@@ -245,14 -239,11 +239,11 @@@ effective[1].toValueType(Number.class)); } - /** Description of results of the {@code "Subtract"} expression. */ + private static final ScopedName NAME = createName(FunctionNames.Subtract); - private static final AttributeType<Number> TYPE = createNumericType(NAME); - @Override protected AttributeType<Number> expectedType() {return TYPE;} ++ private static final DefaultAttributeType<Number> TYPE = createNumericType(NAME); + @Override protected DefaultAttributeType<Number> expectedType() {return TYPE;} - private static final DefaultAttributeType<Number> TYPE = createNumericType(FunctionNames.Subtract); - - /** Representation of the {@code "Subtract"} operation. */ - @Override protected char symbol() {return '−';} @Override public ScopedName getFunctionName() {return NAME;} - private static final ScopedName NAME = createName(FunctionNames.Subtract); + @Override protected char symbol() {return '−';} /** Applies this expression to the given operands. */ @Override protected Number applyAsDouble (double left, double right) {return left - right;} @@@ -291,14 -281,11 +281,11 @@@ effective[1].toValueType(Number.class)); } - /** Description of results of the {@code "Multiply"} expression. */ + private static final ScopedName NAME = createName(FunctionNames.Multiply); - private static final AttributeType<Number> TYPE = createNumericType(NAME); - @Override protected AttributeType<Number> expectedType() {return TYPE;} ++ private static final DefaultAttributeType<Number> TYPE = createNumericType(NAME); + @Override protected DefaultAttributeType<Number> expectedType() {return TYPE;} - private static final DefaultAttributeType<Number> TYPE = createNumericType(FunctionNames.Multiply); - - /** Representation of the {@code "Multiply"} operation. */ - @Override protected char symbol() {return '×';} @Override public ScopedName getFunctionName() {return NAME;} - private static final ScopedName NAME = createName(FunctionNames.Multiply); + @Override protected char symbol() {return '×';} /** Applies this expression to the given operands. */ @Override protected Number applyAsDouble (double left, double right) {return left * right;} @@@ -337,16 -323,13 +323,13 @@@ effective[1].toValueType(Number.class)); } - /** Description of results of the {@code "Divide"} expression. */ + private static final ScopedName NAME = createName(FunctionNames.Divide); - private static final AttributeType<Number> TYPE = createNumericType(NAME); - @Override protected AttributeType<Number> expectedType() {return TYPE;} ++ private static final DefaultAttributeType<Number> TYPE = createNumericType(NAME); + @Override protected DefaultAttributeType<Number> expectedType() {return TYPE;} - private static final DefaultAttributeType<Number> TYPE = createNumericType(FunctionNames.Divide); - - /** Representation of the {@code "Divide"} operation. */ - @Override protected char symbol() {return '÷';} @Override public ScopedName getFunctionName() {return NAME;} - private static final ScopedName NAME = createName(FunctionNames.Divide); + @Override protected char symbol() {return '÷';} - /** Divides the given integers, changing the type if the result is not an integer. */ + /** Applies this expression to the given operands. */ @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);} diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/WraparoundTransformTest.java index 55dd28d31d,ba6dacc002..b9671019fd --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/WraparoundTransformTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/operation/transform/WraparoundTransformTest.java @@@ -31,9 -31,11 +31,11 @@@ import org.junit.jupiter.api.Test import static org.junit.jupiter.api.Assertions.*; import org.apache.sis.test.TestCase; import org.apache.sis.referencing.crs.HardCodedCRS; + import org.apache.sis.referencing.operation.matrix.Matrices; + import org.apache.sis.referencing.operation.matrix.MatrixSIS; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import static org.opengis.test.Assertions.assertMatrixEquals; +// Specific to the main branch: +import static org.apache.sis.test.GeoapiAssert.assertMatrixEquals; /** diff --cc endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java index 4cb4a801bf,d85f3abc94..4bd782842f --- a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java +++ b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java @@@ -1146,12 -1141,12 +1146,12 @@@ public class MetadataBuilder * If two dates are of the same type, retains the latest one if the type name starts with {@code "LATE_"} * or retains the earliest date otherwise. */ - private static void addEarliest(final Collection<CitationDate> dates, final CitationDate date, final DateType type) { + private static void addEarliest(final Collection<CitationDate> dates, final DefaultCitationDate date, final DateType type) { for (final Iterator<CitationDate> it = dates.iterator(); it.hasNext();) { final CitationDate existing = it.next(); - if (type.equals(existing.getDateType())) { + if (type.equals(existing.getDateType()) && existing instanceof DefaultCitationDate) { - final int method = type.name().startsWith("LATE_") ? TimeMethods.BEFORE : TimeMethods.AFTER; - if (TimeMethods.compareAny(method, ((DefaultCitationDate) existing).getReferenceDate(), date.getReferenceDate())) { + TimeMethods.Test method = type.name().startsWith("LATE_") ? TimeMethods.Test.BEFORE : TimeMethods.Test.AFTER; - if (TimeMethods.compareLenient(method, existing.getReferenceDate(), date.getReferenceDate())) { ++ if (TimeMethods.compareLenient(method, ((DefaultCitationDate) existing).getReferenceDate(), date.getReferenceDate())) { it.remove(); break; } diff --cc endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java index acf5924e20,cdb9fbe5ea..230646b086 --- a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java +++ b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/FeatureQueryTest.java @@@ -90,10 -97,10 +91,10 @@@ public final class FeatureQueryTest ext * Creates a simple feature with a property flagged as an identifier. */ private void createFeatureWithIdentifier() { - final FeatureTypeBuilder ftb = new FeatureTypeBuilder().setName("Test"); + final var ftb = new FeatureTypeBuilder().setName("Test"); ftb.addAttribute(String.class).setName("id").addRole(AttributeRole.IDENTIFIER_COMPONENT); - final FeatureType type = ftb.build(); - features = new Feature[] { + final DefaultFeatureType type = ftb.build(); + features = new AbstractFeature[] { type.newInstance() }; features[0].setPropertyValue("id", "id-0"); @@@ -109,9 -116,9 +110,9 @@@ * @return the points created by this method in no particular order. */ private Set<Point2D.Double> createFeaturesWithGeometry(final GeometryLibrary library) { - final FeatureTypeBuilder ftb = new FeatureTypeBuilder(null, library, null).setName("Test"); + final var ftb = new FeatureTypeBuilder(null, library, null).setName("Test"); ftb.addAttribute(GeometryType.POINT).setCRS(HardCodedCRS.WGS84_LATITUDE_FIRST).setName("point"); - final FeatureType type = ftb.build(); + final DefaultFeatureType type = ftb.build(); final var points = new HashSet<Point2D.Double>(); final Geometries<?> factory = Geometries.factory(library); @SuppressWarnings("LocalVariableHidesMemberVariable") @@@ -119,10 -126,10 +120,10 @@@ for (int i=0; i < features.length; i++) { final var point = new Point2D.Double(-10 - i, 20 + i); assertTrue(points.add(point)); - final Feature f = type.newInstance(); + final AbstractFeature f = type.newInstance(); f.setPropertyValue("point", factory.createPoint(point.x, point.y)); features[i] = f; - }; + } this.features = features; featureSet = new MemoryFeatureSet(null, type, Arrays.asList(features)); return points; @@@ -269,6 -291,40 +270,40 @@@ verifyQueryResult(3); } + /** + * Verifies the effect of {@link FeatureQuery#setSelection(Filter)} on a property having a date. + * + * @throws DataStoreException if an error occurred while executing the query. + */ + @Test + public void testSelectionOfDate() throws DataStoreException { + // Prepare the feature instances. + { + final var ftb = new FeatureTypeBuilder().setName("Test"); + ftb.addAttribute(LocalDate.class).setName("value1"); - final FeatureType type = ftb.build(); - features = new Feature[4]; ++ final DefaultFeatureType type = ftb.build(); ++ features = new AbstractFeature[4]; + Arrays.setAll(features, (i) -> { - Feature feature = type.newInstance(); ++ AbstractFeature feature = type.newInstance(); + feature.setPropertyValue("value1", LocalDate.of(2000, 1, 10 + i)); + return feature; + }); + featureSet = new MemoryFeatureSet(null, type, Arrays.asList(features)); + } + // Prepare the query. + { - final FilterFactory<Feature,?,?> ff = DefaultFilterFactory.forFeatures(); ++ final DefaultFilterFactory<AbstractFeature,?,?> ff = DefaultFilterFactory.forFeatures(); + query.setSelection(ff.lessOrEqual( + ff.property("value1", LocalDate.class), + ff.literal(LocalDate.of(2000, 1, 12)))); + assertXPathsEqual("value1"); + } + // Verify the result. + final FeatureSet fs = query.execute(featureSet); - final Feature[] result = fs.features(false).toArray(Feature[]::new); ++ final AbstractFeature[] result = fs.features(false).toArray(AbstractFeature[]::new); + assertArrayEquals(Arrays.copyOf(features, 3), result); + } + /** * Verifies the effect of {@link FeatureQuery#setProjection(FeatureQuery.Column[])}. * diff --cc optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/README.md index e2158b075b,85ccdef170..bb9e8739ef --- a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/README.md +++ b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/README.md @@@ -60,10 -60,13 +60,13 @@@ export NON_FREE_DIR=$PW cd _<path to SIS project directory>_ gradle clean test jar - export CLASSPATH=~/.m2/repository/org/apache/derby/derby/10.14.2.0/derby-10.14.2.0.jar + export CLASSPATH=~/.m2/repository/org/apache/derby/derby/10.15.2.0/derby-10.15.2.0.jar + export CLASSPATH=~/.m2/repository/org/apache/derby/derbyshared/10.15.2.0/derbyshared-10.15.2.0.jar:$CLASSPATH + export CLASSPATH=~/.m2/repository/org/apache/derby/derbytools/10.15.2.0/derbytools-10.15.2.0.jar:$CLASSPATH export CLASSPATH=~/.m2/repository/org/postgresql/postgresql/42.7.7/postgresql-42.7.7.jar:$CLASSPATH export CLASSPATH=~/.m2/repository/javax/measure/unit-api/2.1.3/unit-api-2.1.3.jar:$CLASSPATH + export CLASSPATH=~/.m2/repository/jakarta/xml/bind/jakarta.xml.bind-api/4.0.4/jakarta.xml.bind-api-4.0.4.jar:$CLASSPATH -export CLASSPATH=$PWD/geoapi/snapshot/geoapi/target/geoapi-3.1-SNAPSHOT.jar:$CLASSPATH +export CLASSPATH=$PWD/geoapi/snapshot/geoapi/target/geoapi-3.0.2.jar:$CLASSPATH export CLASSPATH=$PWD/endorsed/build/libs/org.apache.sis.referencing.jar:$CLASSPATH export CLASSPATH=$PWD/endorsed/build/libs/org.apache.sis.metadata.jar:$CLASSPATH export CLASSPATH=$PWD/endorsed/build/libs/org.apache.sis.util.jar:$CLASSPATH
