This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 8b0191c652 More dynamic and more efficient comparisons of temporal
values depending on their type. This is required by the change of period
beginning/ending times to `Temporal`. This work duplicates work already done in
`ComparisonFilter`, but the latter will need to be refactored in order to
delegate to the new temporal filters.
8b0191c652 is described below
commit 8b0191c6527602cd2aa5f99ac937d7ccc70c9d0c
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue May 28 18:29:00 2024 +0200
More dynamic and more efficient comparisons of temporal values depending on
their type.
This is required by the change of period beginning/ending times to
`Temporal`.
This work duplicates work already done in `ComparisonFilter`, but the latter
will need to be refactored in order to delegate to the new temporal filters.
---
.../org/apache/sis/filter/ComparisonFilter.java | 4 +
.../apache/sis/filter/DefaultFilterFactory.java | 68 +-
.../main/org/apache/sis/filter/TemporalFilter.java | 811 ++++-----------------
.../org/apache/sis/filter/TemporalOperation.java | 762 +++++++++++++++++++
.../main/org/apache/sis/filter/TimeMethods.java | 396 ++++++++++
.../org/apache/sis/filter/TemporalFilterTest.java | 23 +-
.../metadata/iso/extent/DefaultTemporalExtent.java | 2 +-
.../apache/sis/pending/temporal/DefaultPeriod.java | 14 +-
.../sis/pending/temporal/TemporalUtilities.java | 26 +-
.../apache/sis/xml/bind/gml/TimePeriodBound.java | 12 +-
.../referencing/datum/DefaultTemporalDatum.java | 4 +-
.../org/apache/sis/referencing/internal/Epoch.java | 6 +-
.../main/org/apache/sis/util/Classes.java | 20 +-
.../main/org/apache/sis/util/resources/Errors.java | 5 +
.../apache/sis/util/resources/Errors.properties | 1 +
.../apache/sis/util/resources/Errors_fr.properties | 1 +
geoapi/snapshot | 2 +-
17 files changed, 1425 insertions(+), 732 deletions(-)
diff --git
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
index 0529727c64..04b5b9d050 100644
---
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
@@ -66,6 +66,8 @@ import org.opengis.filter.BetweenComparisonOperator;
* returning 0 if {@code false} or 1 if {@code true}. Comparisons of other
types is done by overriding
* the {@code compare(…)} methods.</p>
*
+ * @todo Delegate all comparisons of temporal objects to {@link
TemporalFilter}.
+ *
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*
@@ -232,6 +234,8 @@ abstract class ComparisonFilter<R> extends
BinaryFunction<R,Object,Object>
*
* @param left the first object to compare. Must be non-null.
* @param right the second object to compare. Must be non-null.
+ *
+ * @todo Delegate all comparisons of temporal objects to {@link
TemporalFilter}.
*/
private boolean evaluate(Object left, Object right) {
/*
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
index ceb827936b..c020754996 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
@@ -32,6 +32,7 @@ import org.apache.sis.filter.sqlmm.Registry;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.iso.AbstractFactory;
import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.privy.Strings;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import java.util.Iterator;
@@ -66,6 +67,11 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
*/
private final Geometries<G> library;
+ /**
+ * The base class of temporal objects.
+ */
+ private final Class<T> temporal;
+
/**
* The strategy to use for representing a region crossing the
anti-meridian.
*/
@@ -93,14 +99,14 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
* <tr><td>Default</td> <td>{@code java.lang.Object}</td></tr>
* </table>
*
- * <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>
+ * The {@code temporal} argument should be one of the {@link
java.time.temporal.Temporal}
+ * implementation classes. The {@code Temporal.class} or {@code
Object.class} arguments
+ * are also accepted if the temporal class is not known at compile-time,
in which case
+ * it will be determined on a case-by-case basis at runtime. Note the
latter is lightly
+ * more expensive that specifying an implementation class in advance.
*
* @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 temporal type of temporal objects, or {@code Object.class}
for any supported type.
* @param wraparound the strategy to use for representing a region
crossing the anti-meridian.
*/
@SuppressWarnings("unchecked")
@@ -116,9 +122,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
throw new
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2,
"spatial", spatial));
}
}
- if (temporal != Object.class) {
- throw new
IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2,
"temporal", temporal));
- }
+ this.temporal = temporal;
this.wraparound = wraparound;
availableFunctions = new HashMap<>();
}
@@ -126,10 +130,9 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
/**
* Returns a factory operating on {@link Feature} instances.
* The {@linkplain GeometryLibrary geometry library} will be the system
default.
+ * The temporal objects can be {@link java.util.Date} or implementations
of {@link java.time.temporal.Temporal}.
*
* @return factory operating on {@link Feature} instances.
- *
- * @todo The type of temporal objects is not yet determined.
*/
public static FilterFactory<Feature, Object, Object> forFeatures() {
return Features.DEFAULT;
@@ -170,7 +173,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
* 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 temporal type of temporal objects, or {@code
Object.class} for any supported type.
* @param wraparound the strategy to use for representing a region
crossing the anti-meridian.
*
* @see DefaultFilterFactory#forFeatures()
@@ -729,7 +732,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
public TemporalOperator<R> after(final Expression<R, ? extends T> time1,
final Expression<R, ? extends T> time2)
{
- return new TemporalFilter.After<>(time1, time2);
+ return TemporalFilter.create(temporal, TemporalOperation.After::new,
time1, time2);
}
/**
@@ -745,7 +748,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
public TemporalOperator<R> before(final Expression<R, ? extends T> time1,
final Expression<R, ? extends T> time2)
{
- return new TemporalFilter.Before<>(time1, time2);
+ return TemporalFilter.create(temporal, TemporalOperation.Before::new,
time1, time2);
}
/**
@@ -761,7 +764,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
public TemporalOperator<R> begins(final Expression<R, ? extends T> time1,
final Expression<R, ? extends T> time2)
{
- return new TemporalFilter.Begins<>(time1, time2);
+ return TemporalFilter.create(temporal, TemporalOperation.Begins::new,
time1, time2);
}
/**
@@ -777,7 +780,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
public TemporalOperator<R> begunBy(final Expression<R, ? extends T> time1,
final Expression<R, ? extends T> time2)
{
- return new TemporalFilter.BegunBy<>(time1, time2);
+ return TemporalFilter.create(temporal, TemporalOperation.BegunBy::new,
time1, time2);
}
/**
@@ -793,7 +796,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
public TemporalOperator<R> tcontains(final Expression<R, ? extends T>
time1,
final Expression<R, ? extends T>
time2)
{
- return new TemporalFilter.Contains<>(time1, time2);
+ return TemporalFilter.create(temporal,
TemporalOperation.Contains::new, time1, time2);
}
/**
@@ -809,7 +812,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
public TemporalOperator<R> during(final Expression<R, ? extends T> time1,
final Expression<R, ? extends T> time2)
{
- return new TemporalFilter.During<>(time1, time2);
+ return TemporalFilter.create(temporal, TemporalOperation.During::new,
time1, time2);
}
/**
@@ -825,7 +828,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
public TemporalOperator<R> tequals(final Expression<R, ? extends T> time1,
final Expression<R, ? extends T> time2)
{
- return new TemporalFilter.Equals<>(time1, time2);
+ return TemporalFilter.create(temporal, TemporalOperation.Equals::new,
time1, time2);
}
/**
@@ -841,7 +844,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
public TemporalOperator<R> toverlaps(final Expression<R, ? extends T>
time1,
final Expression<R, ? extends T>
time2)
{
- return new TemporalFilter.Overlaps<>(time1, time2);
+ return TemporalFilter.create(temporal,
TemporalOperation.Overlaps::new, time1, time2);
}
/**
@@ -857,7 +860,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
public TemporalOperator<R> meets(final Expression<R, ? extends T> time1,
final Expression<R, ? extends T> time2)
{
- return new TemporalFilter.Meets<>(time1, time2);
+ return TemporalFilter.create(temporal, TemporalOperation.Meets::new,
time1, time2);
}
/**
@@ -873,7 +876,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
public TemporalOperator<R> ends(final Expression<R, ? extends T> time1,
final Expression<R, ? extends T> time2)
{
- return new TemporalFilter.Ends<>(time1, time2);
+ return TemporalFilter.create(temporal, TemporalOperation.Ends::new,
time1, time2);
}
/**
@@ -889,7 +892,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
public TemporalOperator<R> overlappedBy(final Expression<R, ? extends T>
time1,
final Expression<R, ? extends T>
time2)
{
- return new TemporalFilter.OverlappedBy<>(time1, time2);
+ return TemporalFilter.create(temporal,
TemporalOperation.OverlappedBy::new, time1, time2);
}
/**
@@ -905,7 +908,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
public TemporalOperator<R> metBy(final Expression<R, ? extends T> time1,
final Expression<R, ? extends T> time2)
{
- return new TemporalFilter.MetBy<>(time1, time2);
+ return TemporalFilter.create(temporal, TemporalOperation.MetBy::new,
time1, time2);
}
/**
@@ -921,7 +924,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
public TemporalOperator<R> endedBy(final Expression<R, ? extends T> time1,
final Expression<R, ? extends T> time2)
{
- return new TemporalFilter.EndedBy<>(time1, time2);
+ return TemporalFilter.create(temporal, TemporalOperation.EndedBy::new,
time1, time2);
}
/**
@@ -938,7 +941,7 @@ public abstract class DefaultFilterFactory<R,G,T> extends
AbstractFactory implem
public TemporalOperator<R> anyInteracts(final Expression<R, ? extends T>
time1,
final Expression<R, ? extends T>
time2)
{
- return new TemporalFilter.AnyInteracts<>(time1, time2);
+ return TemporalFilter.create(temporal,
TemporalOperation.AnyInteracts::new, time1, time2);
}
/**
@@ -1152,4 +1155,17 @@ public abstract class DefaultFilterFactory<R,G,T>
extends AbstractFactory implem
public SortProperty<R> sort(final ValueReference<R,?> property, final
SortOrder order) {
return new DefaultSortProperty<>(property, order);
}
+
+ /**
+ * Returns a string representation of this factory for debugging purposes.
+ * The string returned by this method may change in any future version.
+ *
+ * @return a string representation for debugging purposes.
+ *
+ * @since 1.5
+ */
+ @Override
+ public String toString() {
+ return Strings.toString(getClass(), "spatial", library.library,
"temporal", temporal.getSimpleName());
+ }
}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
index f6e567be64..88ebd77415 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java
@@ -16,7 +16,8 @@
*/
package org.apache.sis.filter;
-import java.time.Instant;
+import org.apache.sis.util.Classes;
+import org.apache.sis.feature.privy.FeatureExpression;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.temporal.Period;
@@ -27,743 +28,247 @@ import org.opengis.filter.TemporalOperatorName;
/**
- * Temporal operations between a period and an instant.
- * The nature of the operation depends on the subclass.
- * Subclasses shall override at least one of following methods:
- *
- * <ul>
- * <li>{@link #evaluate(Instant, Instant)}</li>
- * <li>{@link #evaluate(Period, Instant)}</li>
- * <li>{@link #evaluate(Period, Period)}</li>
- * </ul>
+ * Temporal operations between a period and an instant or between two periods.
+ * The base class represents the general case when don't know if the the
argument are periods or not.
+ * The subclasses represent specializations when at least one of the arguments
is known to be a period.
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*
- * @param <T> the type of resources (e.g. {@link
org.opengis.feature.Feature}) used as inputs.
+ * @param <R> the type of resources (e.g. {@link
org.opengis.feature.Feature}) used as inputs.
+ * @param <T> the base type of temporal objects, or {@code Object.class} for
any type.
*/
-abstract class TemporalFilter<T> extends BinaryFunction<T,Object,Object>
- implements TemporalOperator<T>, Optimization.OnFilter<T>
+class TemporalFilter<R,T> extends BinaryFunction<R,T,T>
+ implements TemporalOperator<R>, Optimization.OnFilter<R>
{
/**
* For cross-version compatibility.
*/
- private static final long serialVersionUID = 5392780837658687513L;
+ private static final long serialVersionUID = 8248634286785309435L;
+
+ /**
+ * The operation to apply on instants or periods.
+ */
+ protected final TemporalOperation<T> operation;
/**
* Creates a new temporal function.
*
+ * @param operation the operation to apply on instants or periods.
* @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.
*/
- TemporalFilter(final Expression<T,?> expression1,
- final Expression<T,?> expression2)
+ private TemporalFilter(final TemporalOperation<T> operation,
+ final Expression<R, ? extends T> expression1,
+ final Expression<R, ? extends T> expression2)
{
super(expression1, expression2);
+ this.operation = operation;
}
/**
- * Returns {@code true} if {@code self} is non null and before {@code
other}.
- * This is an helper function for {@code evaluate(…)} methods
implementations.
- */
- private static boolean isBefore(final Instant self, final Instant other) {
- return (self != null) && (other != null) && self.isBefore(other);
- }
-
- /**
- * Returns {@code true} if {@code self} is non null and after {@code
other}.
- * This is an helper function for {@code evaluate(…)} methods
implementations.
- */
- private static boolean isAfter(final Instant self, final Instant other) {
- return (self != null) && (other != null) && self.isAfter(other);
- }
-
- /**
- * Returns {@code true} if {@code self} is non null and equal to {@code
other}.
- * This is an helper function for {@code evaluate(…)} methods
implementations.
- */
- private static boolean isEqual(final Instant self, final Instant other) {
- return (self != null) && self.equals(other);
- }
-
- /**
- * Determines if the test(s) represented by this filter passes with the
given operands.
- * Values of {@link #expression1} and {@link #expression2} shall be two
single values.
+ * Creates a new temporal function.
+ *
+ * @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.
+ * @param operation the operation to apply on instants or periods.
*/
- @Override
- public final boolean test(final T candidate) {
- final Object left = expression1.apply(candidate);
- if (left instanceof Period) {
- final Object right = expression2.apply(candidate);
- if (right instanceof Period) {
- return evaluate((Period) left, (Period) right);
- }
- final Instant t = ComparisonFilter.toInstant(right);
- if (t != null) {
- return evaluate((Period) left, t);
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public static <R,V> TemporalFilter<R,?> create(
+ final Class<V> type,
+ final TemporalOperation.Factory factory,
+ final Expression<R, ? extends V> expression1,
+ final Expression<R, ? extends V> expression2)
+ {
+ final Class<? extends V> c1 = getValueClass(expression1, type);
+ final Class<? extends V> c2 = getValueClass(expression2, type);
+ Class<? extends V> commonType = type;
+ if (type.isInterface()) {
+ for (final Class<?> c : Classes.findCommonInterfaces(c1, c2)) {
+ if (commonType.isAssignableFrom(c)) {
+ commonType = (Class<? extends V>) c; // Safe
because verified by `isAssignableFrom(c)`.
+ }
}
} else {
- final Instant t = ComparisonFilter.toInstant(left);
- if (t != null) {
- final Instant t2 =
ComparisonFilter.toInstant(expression2.apply(candidate));
- if (t2 != null) {
- return evaluate(t, t2);
- }
+ final Class<?> c = Classes.findCommonClass(c1, c2);
+ if (commonType.isAssignableFrom(c)) {
+ commonType = (Class<? extends V>) c; // Safe
because verified by `isAssignableFrom(c)`.
}
}
- return false;
- }
-
- /**
- * Evaluates the filter between two instants.
- * Both arguments given to this method are non-null.
- * The {@code self} and {@code other} argument names are chosen to match
ISO 19108 tables.
- */
- protected boolean evaluate(Instant self, Instant other) {
- return false;
- }
-
- /**
- * Evaluates the filter between a period and an instant.
- * Both arguments given to this method are non-null, but period begin or
end instant may be null.
- * The {@code self} and {@code other} argument names are chosen to match
ISO 19108 tables.
- */
- protected boolean evaluate(Period self, Instant other) {
- return false;
- }
-
- /**
- * Evaluates the filter between two periods.
- * Both arguments given to this method are non-null, but period begin or
end instant may be null.
- * The {@code self} and {@code other} argument names are chosen to match
ISO 19108 tables.
- */
- protected boolean evaluate(Period self, Period other) {
- return false;
- }
-
- /*
- * No operation on numbers for now. We could revisit this policy in a
future version if we
- * allow the temporal function to have a CRS and to operate on temporal
coordinate values.
- */
-
-
- /**
- * The {@code "TEquals"} (=) filter. Defined by ISO 19108 as:
- * <ul>
- * <li>{@literal self = other}</li>
- * <li>{@literal self.begin = other.begin AND self.end =
other.end}</li>
- * </ul>
- *
- * @param <T> the type of resources used as inputs.
- */
- static final class Equals<T> extends TemporalFilter<T> {
- /** For cross-version compatibility during (de)serialization. */
- private static final long serialVersionUID = -6060822291802339424L;
-
- /** Creates a new filter. */
- Equals(Expression<T,?> expression1,
- Expression<T,?> expression2)
- {
- super(expression1, expression2);
- }
-
- /** Creates a new filter of the same type but different parameters. */
- @Override public Filter<T> recreate(final Expression<T,?>[] effective)
{
- return new Equals<>(effective[0], effective[1]);
- }
-
- /** Identification of this operation. */
- @Override public TemporalOperatorName getOperatorType() {
- return TemporalOperatorName.EQUALS;
- }
-
- /** Symbol of this operation. */
- @Override protected char symbol() {
- return '=';
- }
-
- /** Condition defined by ISO 19108:2002 §5.2.3.5. */
- @Override protected boolean evaluate(final Instant self, final Instant
other) {
- return self.equals(other);
- }
-
- /** Extension to ISO 19108: handle instant as a tiny period. */
- @Override public boolean evaluate(final Period self, final Instant
other) {
- return isEqual(self.getBeginning(), other) &&
- isEqual(self.getEnding(), other);
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Period
other) {
- return isEqual(self.getBeginning(), other.getBeginning()) &&
- isEqual(self.getEnding(), other.getEnding());
- }
- }
-
-
- /**
- * The {@code "Before"} {@literal (<)} filter. Defined by ISO 19108 as:
- * <ul>
- * <li>{@literal self < other}</li>
- * <li>{@literal self.end < other}</li>
- * <li>{@literal self.end < other.begin}</li>
- * </ul>
- *
- * @param <T> the type of resources used as inputs.
- */
- static final class Before<T> extends TemporalFilter<T> {
- /** For cross-version compatibility during (de)serialization. */
- private static final long serialVersionUID = -3422629447456003982L;
-
- /** Creates a new filter. */
- Before(Expression<T,?> expression1,
- Expression<T,?> expression2)
- {
- super(expression1, expression2);
- }
-
- /** Creates a new filter of the same type but different parameters. */
- @Override public Filter<T> recreate(final Expression<T,?>[] effective)
{
- return new Before<>(effective[0], effective[1]);
- }
-
- /** Identification of this operation. */
- @Override public TemporalOperatorName getOperatorType() {
- return TemporalOperatorName.BEFORE;
- }
-
- /** Symbol of this operation. */
- @Override protected char symbol() {
- return '<';
- }
-
- /** Condition defined by ISO 19108:2002 §5.2.3.5. */
- @Override protected boolean evaluate(final Instant self, final Instant
other) {
- return self.isBefore(other);
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Instant
other) {
- return isBefore(self.getEnding(), other);
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Period
other) {
- return isBefore(self.getEnding(), other.getBeginning());
- }
- }
-
-
- /**
- * The {@code "After"} {@literal (>)} filter. Defined by ISO 19108 as:
- * <ul>
- * <li>{@literal self > other}</li>
- * <li>{@literal self.begin > other}</li>
- * <li>{@literal self.begin > other.end}</li>
- * </ul>
- *
- * @param <T> the type of resources used as inputs.
- */
- static final class After<T> extends TemporalFilter<T> {
- /** For cross-version compatibility during (de)serialization. */
- private static final long serialVersionUID = 5410476260417497682L;
-
- /** Creates a new filter. */
- After(Expression<T,?> expression1,
- Expression<T,?> expression2)
- {
- super(expression1, expression2);
- }
-
- /** Creates a new filter of the same type but different parameters. */
- @Override public Filter<T> recreate(final Expression<T,?>[] effective)
{
- return new After<>(effective[0], effective[1]);
- }
-
- /** Identification of this operation. */
- @Override public TemporalOperatorName getOperatorType() {
- return TemporalOperatorName.AFTER;
- }
-
- /** Symbol of this operation. */
- @Override protected char symbol() {
- return '>';
- }
-
- /** Condition defined by ISO 19108:2002 §5.2.3.5. */
- @Override protected boolean evaluate(final Instant self, final Instant
other) {
- return self.isAfter(other);
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Instant
other) {
- return isAfter(self.getBeginning(), other);
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Period
other) {
- return isAfter(self.getBeginning(), other.getEnding());
- }
- }
-
-
- /**
- * The {@code "Begins"} filter. Defined by ISO 19108 as:
- * <ul>
- * <li>{@literal self.begin = other.begin AND self.end <
other.end}</li>
- * </ul>
- *
- * @param <T> the type of resources used as inputs.
- */
- static final class Begins<T> extends TemporalFilter<T> {
- /** For cross-version compatibility during (de)serialization. */
- private static final long serialVersionUID = -7880699329127762233L;
-
- /** Creates a new filter. */
- Begins(Expression<T,?> expression1,
- Expression<T,?> expression2)
- {
- super(expression1, expression2);
- }
-
- /** Creates a new filter of the same type but different parameters. */
- @Override public Filter<T> recreate(final Expression<T,?>[] effective)
{
- return new Begins<>(effective[0], effective[1]);
- }
-
- /** Identification of this operation. */
- @Override public TemporalOperatorName getOperatorType() {
- return TemporalOperatorName.BEGINS;
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Period
other) {
- return isEqual (self.getBeginning(), other.getBeginning()) &&
- isBefore(self.getEnding(), other.getEnding());
- }
+ /*
+ * We cannot use a more specific type here because the `find(…)`
method argument is parameterized
+ * with `<? extends T>` while its return value is parameterized with
`<? super T>`. Java language
+ * has no parameterized type that can express those conflicting
covariance and contra-variance,
+ * therefore we must use `<?>`.
+ *
+ * Creations of `TemporalFilter` instances below are safe because
`TimeMethods.type` is a parent
+ * of both `expression1` and `expression2` value types (verified by
assertions). Therefore, with
+ * `commonType` of type `Class<T>` no matter if <T> is a super-type or
a sub-type of <V>, we can
+ * assert that the parmeterized type of the two expressions is `<?
extends T>`.
+ */
+ final TemporalOperation<?> operation =
factory.create(TimeMethods.find(commonType)).unique();
+ assert operation.comparators.type.isAssignableFrom(commonType) :
commonType;
+ assert commonType.isAssignableFrom(c1) : c1;
+ assert commonType.isAssignableFrom(c2) : c2;
+ if (Period.class.isAssignableFrom(commonType)) {
+ // Safe because `commonType` extends both Period and T.
+ return new Periods(operation, expression1, expression2);
+ }
+ if (operation.comparators.isDynamic()) {
+ return new TemporalFilter(operation, expression1, expression2);
+ }
+ return new Instants(operation, expression1, expression2);
}
-
/**
- * The {@code "Ends"} filter. Defined by ISO 19108 as:
- * <ul>
- * <li>{@literal self.begin > other.begin AND self.end =
other.end}</li>
- * </ul>
- *
- * @param <T> the type of resources used as inputs.
+ * Returns the class of values computed by the given expression, or {@code
type} if unknown.
*/
- static final class Ends<T> extends TemporalFilter<T> {
- /** For cross-version compatibility during (de)serialization. */
- private static final long serialVersionUID = -5508229966320563437L;
-
- /** Creates a new filter. */
- Ends(Expression<T,?> expression1,
- Expression<T,?> expression2)
- {
- super(expression1, expression2);
- }
-
- /** Creates a new filter of the same type but different parameters. */
- @Override public Filter<T> recreate(final Expression<T,?>[] effective)
{
- return new Ends<>(effective[0], effective[1]);
- }
-
- /** Identification of this operation. */
- @Override public TemporalOperatorName getOperatorType() {
- return TemporalOperatorName.ENDS;
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Period
other) {
- return isEqual(self.getEnding(), other.getEnding()) &&
- isAfter(self.getBeginning(), other.getBeginning());
- }
- }
-
-
- /**
- * The {@code "BegunBy"} filter. Defined by ISO 19108 as:
- * <ul>
- * <li>{@literal self.begin = other}</li>
- * <li>{@literal self.begin = other.begin AND self.end >
other.end}</li>
- * </ul>
- *
- * @param <T> the type of resources used as inputs.
- */
- static final class BegunBy<T> extends TemporalFilter<T> {
- /** For cross-version compatibility during (de)serialization. */
- private static final long serialVersionUID = -7212413827394364384L;
-
- /** Creates a new filter. */
- BegunBy(Expression<T,?> expression1,
- Expression<T,?> expression2)
- {
- super(expression1, expression2);
- }
-
- /** Creates a new filter of the same type but different parameters. */
- @Override public Filter<T> recreate(final Expression<T,?>[] effective)
{
- return new BegunBy<>(effective[0], effective[1]);
- }
-
- /** Identification of this operation. */
- @Override public TemporalOperatorName getOperatorType() {
- return TemporalOperatorName.BEGUN_BY;
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Instant
other) {
- return isEqual(self.getBeginning(), other);
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Period
other) {
- return isEqual(self.getBeginning(), other.getBeginning()) &&
- isAfter(self.getEnding(), other.getEnding());
- }
- }
-
-
- /**
- * The {@code "EndedBy"} filter. Defined by ISO 19108 as:
- * <ul>
- * <li>{@literal self.end = other}</li>
- * <li>{@literal self.begin < other.begin AND self.end =
other.end}</li>
- * </ul>
- *
- * @param <T> the type of resources used as inputs.
- */
- static final class EndedBy<T> extends TemporalFilter<T> {
- /** For cross-version compatibility during (de)serialization. */
- private static final long serialVersionUID = 8586566103462153666L;
-
- /** Creates a new filter. */
- EndedBy(Expression<T,?> expression1,
- Expression<T,?> expression2)
- {
- super(expression1, expression2);
- }
-
- /** Creates a new filter of the same type but different parameters. */
- @Override public Filter<T> recreate(final Expression<T,?>[] effective)
{
- return new EndedBy<>(effective[0], effective[1]);
- }
-
- /** Identification of this operation. */
- @Override public TemporalOperatorName getOperatorType() {
- return TemporalOperatorName.ENDED_BY;
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Instant
other) {
- return isEqual(self.getEnding(), other);
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Period
other) {
- return isEqual (self.getEnding(), other.getEnding()) &&
- isBefore(self.getBeginning(), other.getBeginning());
+ @SuppressWarnings("unchecked")
+ private static <T> Class<? extends T> getValueClass(final Expression<?,?
extends T> e, final Class<T> type) {
+ if (e instanceof FeatureExpression<?,?>) {
+ final Class<?> c = ((FeatureExpression<?, ? extends T>)
e).getValueClass();
+ if (type.isAssignableFrom(c)) {
+ return (Class<? extends T>) c;
+ }
}
+ return type;
}
-
/**
- * The {@code "Meets"} filter. Defined by ISO 19108 as:
- * <ul>
- * <li>{@literal self.end = other.begin}</li>
- * </ul>
- *
- * @param <T> the type of resources used as inputs.
+ * Returns an identification of this operation.
*/
- static final class Meets<T> extends TemporalFilter<T> {
- /** For cross-version compatibility during (de)serialization. */
- private static final long serialVersionUID = -3534843269384858443L;
-
- /** Creates a new filter. */
- Meets(Expression<T,?> expression1,
- Expression<T,?> expression2)
- {
- super(expression1, expression2);
- }
-
- /** Creates a new filter of the same type but different parameters. */
- @Override public Filter<T> recreate(final Expression<T,?>[] effective)
{
- return new Meets<>(effective[0], effective[1]);
- }
-
- /** Identification of this operation. */
- @Override public TemporalOperatorName getOperatorType() {
- return TemporalOperatorName.MEETS;
- }
-
- /** Extension to ISO 19108: handle instant as a tiny period. */
- @Override public boolean evaluate(final Instant self, final Instant
other) {
- return self.equals(other);
- }
-
- /** Extension to ISO 19108: handle instant as a tiny period. */
- @Override public boolean evaluate(final Period self, final Instant
other) {
- return isEqual(self.getEnding(), other);
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Period
other) {
- return isEqual(self.getEnding(), other.getBeginning());
- }
+ @Override
+ public final TemporalOperatorName getOperatorType() {
+ return operation.getOperatorType();
}
-
/**
- * The {@code "MetBy"} filter. Defined by ISO 19108 as:
- * <ul>
- * <li>{@literal self.begin = other.end}</li>
- * </ul>
+ * Returns the mathematical symbol for this temporal operation.
*
- * @param <T> the type of resources used as inputs.
+ * @return the mathematical symbol, or 0 if none.
*/
- static final class MetBy<T> extends TemporalFilter<T> {
- /** For cross-version compatibility during (de)serialization. */
- private static final long serialVersionUID = 5358059498707330482L;
-
- /** Creates a new filter. */
- MetBy(Expression<T,?> expression1,
- Expression<T,?> expression2)
- {
- super(expression1, expression2);
- }
-
- /** Creates a new filter of the same type but different parameters. */
- @Override public Filter<T> recreate(final Expression<T,?>[] effective)
{
- return new MetBy<>(effective[0], effective[1]);
- }
-
- /** Identification of this operation. */
- @Override public TemporalOperatorName getOperatorType() {
- return TemporalOperatorName.MET_BY;
- }
-
- /** Extension to ISO 19108: handle instant as a tiny period. */
- @Override public boolean evaluate(final Instant self, final Instant
other) {
- return self.equals(other);
- }
-
- /** Extension to ISO 19108: handle instant as a tiny period. */
- @Override public boolean evaluate(final Period self, final Instant
other) {
- return isEqual(self.getBeginning(), other);
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Period
other) {
- return isEqual(self.getBeginning(), other.getEnding());
- }
+ @Override
+ protected final char symbol() {
+ return operation.symbol();
}
-
/**
- * The {@code "During"} filter. Defined by ISO 19108 as:
- * <ul>
- * <li>{@literal self.begin > other.begin AND self.end <
other.end}</li>
- * </ul>
+ * Casts an expression returning values of unknown type.
+ * This is an helper function for {@code recreate(…)} method
implementations.
*
- * @param <T> the type of resources used as inputs.
+ * @param effective the expression to cast.
+ * @return an expression that can be used with this temporal filter.
+ * @throws ClassCastException if the expression cannot be casted.
*/
- static final class During<T> extends TemporalFilter<T> {
- /** For cross-version compatibility during (de)serialization. */
- private static final long serialVersionUID = -4674319635076886196L;
-
- /** Creates a new filter. */
- During(Expression<T,?> expression1,
- Expression<T,?> expression2)
- {
- super(expression1, expression2);
- }
-
- /** Creates a new filter of the same type but different parameters. */
- @Override public Filter<T> recreate(final Expression<T,?>[] effective)
{
- return new During<>(effective[0], effective[1]);
- }
-
- /** Identification of this operation. */
- @Override public TemporalOperatorName getOperatorType() {
- return TemporalOperatorName.DURING;
- }
-
- /** Symbol of this operation. */
- @Override protected char symbol() {
- return '⊊'; // `self` is a proper (or strict) subset of
`other`.
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Period
other) {
- return isAfter (self.getBeginning(), other.getBeginning()) &&
- isBefore(self.getEnding(), other.getEnding());
- }
+ protected final Expression<R, ? extends T> cast(final Expression<R,?>
effective) {
+ return effective.toValueType(operation.comparators.type);
}
-
/**
- * The {@code "TContains"} filter. Defined by ISO 19108 as:
- * <ul>
- * <li>{@literal self.begin < other AND self.end > other}</li>
- * <li>{@literal self.begin < other.begin AND self.end >
other.end}</li>
- * </ul>
- *
- * @param <T> the type of resources used as inputs.
+ * Creates a new filter of the same type but different parameters.
*/
- static final class Contains<T> extends TemporalFilter<T> {
- /** For cross-version compatibility during (de)serialization. */
- private static final long serialVersionUID = 9107531246948034411L;
-
- /** Creates a new filter. */
- Contains(Expression<T,?> expression1,
- Expression<T,?> expression2)
- {
- super(expression1, expression2);
- }
-
- /** Creates a new filter of the same type but different parameters. */
- @Override public Filter<T> recreate(final Expression<T,?>[] effective)
{
- return new Contains<>(effective[0], effective[1]);
- }
-
- /** Identification of this operation. */
- @Override public TemporalOperatorName getOperatorType() {
- return TemporalOperatorName.CONTAINS;
- }
-
- /** Symbol of this operation. */
- @Override protected char symbol() {
- return '⊋'; // `self` is a proper (or strict) superset of
`other`.
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Instant
other) {
- return isBefore(self.getBeginning(), other) &&
- isAfter (self.getEnding(), other);
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Period
other) {
- return isBefore(self.getBeginning(), other.getBeginning()) &&
- isAfter (self.getEnding(), other.getEnding());
- }
+ @Override
+ public Filter<R> recreate(final Expression<R,?>[] effective) {
+ return new TemporalFilter<>(operation, cast(effective[0]),
cast(effective[1]));
}
-
/**
- * The {@code "TOverlaps"} filter. Defined by ISO 19108 as:
- * <ul>
- * <li>{@literal self.begin < other.begin AND self.end > other.begin
AND self.end < other.end}</li>
- * </ul>
- *
- * @param <T> the type of resources used as inputs.
+ * Determines if the test(s) represented by this filter passes with the
given operands.
+ * Values of {@link #expression1} and {@link #expression2} shall be two
single values.
*/
- static final class Overlaps<T> extends TemporalFilter<T> {
- /** For cross-version compatibility during (de)serialization. */
- private static final long serialVersionUID = 1517443045593389773L;
-
- /** Creates a new filter. */
- Overlaps(Expression<T,?> expression1,
- Expression<T,?> expression2)
- {
- super(expression1, expression2);
- }
-
- /** Creates a new filter of the same type but different parameters. */
- @Override public Filter<T> recreate(final Expression<T,?>[] effective)
{
- return new Overlaps<>(effective[0], effective[1]);
- }
-
- /** Identification of this operation. */
- @Override public TemporalOperatorName getOperatorType() {
- return TemporalOperatorName.OVERLAPS;
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Period
other) {
- final Instant selfBegin, selfEnd, otherBegin, otherEnd;
- return ((otherBegin = other.getBeginning()) != null) &&
- ((selfBegin = self .getBeginning()) != null) &&
selfBegin.isBefore(otherBegin) &&
- ((selfEnd = self .getEnding()) != null) && selfEnd
.isAfter (otherBegin) &&
- ((otherEnd = other.getEnding()) != null) && otherEnd
.isAfter (selfEnd);
+ @Override
+ public boolean test(final R candidate) {
+ final T left = expression1.apply(candidate);
+ if (left != null) {
+ final T right = expression2.apply(candidate);
+ if (right != null) {
+ if (left instanceof Period) {
+ if (right instanceof Period) {
+ return operation.evaluate((Period) left, (Period)
right);
+ } else {
+ return operation.evaluate((Period) left, right);
+ }
+ } else if (right instanceof Period) {
+ return operation.evaluate(left, (Period) right);
+ } else {
+ return operation.evaluate(left, right);
+ }
+ }
}
+ return false;
}
/**
- * The {@code "OverlappedBy"} filter. Defined by ISO 19108 as:
- * <ul>
- * <li>{@literal self.begin > other.begin AND self.begin < other.end
AND self.end > other.end}</li>
- * </ul>
+ * A temporal filters where both operands are ISO 19108 instants.
*
- * @param <T> the type of resources used as inputs.
+ * @param <R> the type of resources used as inputs.
+ * @param <T> the base type of temporal objects.
*/
- static final class OverlappedBy<T> extends TemporalFilter<T> {
+ private static final class Instants<R,T> extends TemporalFilter<R,T> {
/** For cross-version compatibility during (de)serialization. */
- private static final long serialVersionUID = 2228673820507226463L;
+ private static final long serialVersionUID = -3176521794130878518L;
/** Creates a new filter. */
- OverlappedBy(Expression<T,?> expression1,
- Expression<T,?> expression2)
+ Instants(TemporalOperation<T> operation,
+ Expression<R, ? extends T> expression1,
+ Expression<R, ? extends T> expression2)
{
- super(expression1, expression2);
+ super(operation, expression1, expression2);
}
/** Creates a new filter of the same type but different parameters. */
- @Override public Filter<T> recreate(final Expression<T,?>[] effective)
{
- return new OverlappedBy<>(effective[0], effective[1]);
+ @Override public Filter<R> recreate(final Expression<R,?>[] effective)
{
+ return new Instants<>(operation, cast(effective[0]),
cast(effective[1]));
}
- /** Identification of this operation. */
- @Override public TemporalOperatorName getOperatorType() {
- return TemporalOperatorName.OVERLAPPED_BY;
- }
-
- /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
- @Override public boolean evaluate(final Period self, final Period
other) {
- final Instant selfBegin, selfEnd, otherBegin, otherEnd;
- return ((selfBegin = self .getBeginning()) != null) &&
- ((otherBegin = other.getBeginning()) != null) &&
otherBegin.isBefore(selfBegin) &&
- ((otherEnd = other.getEnding()) != null) && selfBegin
.isBefore(otherEnd) &&
- ((selfEnd = self .getEnding()) != null) && selfEnd
.isAfter (otherEnd);
+ /** Tests if this filter passes on the given resource. */
+ @Override public boolean test(final R candidate) {
+ final T left = expression1.apply(candidate);
+ if (left != null) {
+ final T right = expression2.apply(candidate);
+ if (right != null) {
+ return operation.evaluate(left, right);
+ }
+ }
+ return false;
}
}
/**
- * The {@code "AnyInteracts"} filter.
- * This is a shortcut for NOT (Before OR Meets OR MetBy OR After).
+ * A temporal filters where both operands are ISO 19108 periods.
*
- * @param <T> the type of resources used as inputs.
+ * @param <R> the type of resources used as inputs.
+ * @param <T> the base type of temporal objects.
*/
- static final class AnyInteracts<T> extends TemporalFilter<T> {
+ private static final class Periods<R, T extends Period> extends
TemporalFilter<R,T> {
/** For cross-version compatibility during (de)serialization. */
- private static final long serialVersionUID = 5972351564286442392L;
+ private static final long serialVersionUID = 7570449007668484459L;
/** Creates a new filter. */
- AnyInteracts(Expression<T,?> expression1,
- Expression<T,?> expression2)
+ Periods(TemporalOperation<T> operation,
+ Expression<R, ? extends T> expression1,
+ Expression<R, ? extends T> expression2)
{
- super(expression1, expression2);
+ super(operation, expression1, expression2);
}
/** Creates a new filter of the same type but different parameters. */
- @Override public Filter<T> recreate(final Expression<T,?>[] effective)
{
- return new AnyInteracts<>(effective[0], effective[1]);
+ @Override public Filter<R> recreate(final Expression<R,?>[] effective)
{
+ return new Periods<>(operation, cast(effective[0]),
cast(effective[1]));
}
- /** Identification of this operation. */
- @Override public TemporalOperatorName getOperatorType() {
- return TemporalOperatorName.ANY_INTERACTS;
- }
-
- /** Condition defined by OGC filter specification. */
- @Override public boolean evaluate(final Period self, final Period
other) {
- final Instant selfBegin, selfEnd, otherBegin, otherEnd;
- return ((selfBegin = self .getBeginning()) != null) &&
- ((otherEnd = other.getEnding()) != null) &&
selfBegin.isBefore(otherEnd) &&
- ((selfEnd = self .getEnding()) != null) &&
- ((otherBegin = other.getBeginning()) != null) &&
selfEnd.isAfter(otherBegin);
+ /** Tests if this filter passes on the given resource. */
+ @Override public boolean test(final R candidate) {
+ final Period left = expression1.apply(candidate);
+ if (left != null) {
+ final Period right = expression2.apply(candidate);
+ if (right != null) {
+ return operation.evaluate(left, right);
+ }
+ }
+ return false;
}
}
}
diff --git
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
new file mode 100644
index 0000000000..d826f84485
--- /dev/null
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalOperation.java
@@ -0,0 +1,762 @@
+/*
+ * 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 java.time.temporal.Temporal;
+import org.apache.sis.util.Classes;
+import org.apache.sis.util.privy.Strings;
+import org.apache.sis.util.collection.WeakHashSet;
+import static org.apache.sis.filter.TimeMethods.BEFORE;
+import static org.apache.sis.filter.TimeMethods.AFTER;
+import static org.apache.sis.filter.TimeMethods.EQUAL;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.temporal.Period;
+import org.opengis.filter.TemporalOperatorName;
+
+
+/**
+ * Temporal operations between periods and/or instants.
+ * The nature of the operation depends on the subclass.
+ * Subclasses shall override at least one of the following methods:
+ *
+ * <ul>
+ * <li>{@link #evaluate(T, T)}</li>
+ * <li>{@link #evaluate(T, Period)}</li>
+ * <li>{@link #evaluate(Period, T)}</li>
+ * <li>{@link #evaluate(Period, Period)}</li>
+ * </ul>
+ *
+ * Instances of this classes are immutable and thread-safe.
+ * The same instances are shared by many filters.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
+ *
+ * @param <T> the base type of temporal objects, or {@code Object.class} for
any type.
+ */
+abstract class TemporalOperation<T> implements Serializable {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = -5304814915639460679L;
+
+ /**
+ * Temporal operations created in the current JVM.
+ */
+ @SuppressWarnings("unchecked")
+ private static final WeakHashSet<TemporalOperation<?>> POOL = new
WeakHashSet<>((Class) TemporalOperation.class);
+
+ /**
+ * The set of methods to invoke for performing "is before", "is after" or
"is equal" comparisons.
+ */
+ protected final TimeMethods<T> comparators;
+
+ /**
+ * Creates a new temporal operation.
+ *
+ * @param comparators the set of methods to invoke for performing
comparisons.
+ */
+ protected TemporalOperation(final TimeMethods<T> comparators) {
+ this.comparators = comparators;
+ }
+
+ /**
+ * Returns a unique, shared instance of this operation.
+ *
+ * @return a unique instance equal to {@code this}.
+ */
+ public final TemporalOperation<T> unique() {
+ return POOL.unique(this);
+ }
+
+ /**
+ * Returns a hash code value for this operation.
+ * Used for {@link #unique()} implementation.
+ */
+ @Override
+ public final int hashCode() {
+ return getClass().hashCode() + 31 * comparators.type.hashCode();
+ }
+
+ /**
+ * Compares the given object with this operation for equality.
+ * Used for {@link #unique()} implementation.
+ */
+ @Override
+ public final boolean equals(final Object other) {
+ return (other != null) && (other.getClass() == getClass()) &&
+ ((TemporalOperation) other).comparators.type ==
comparators.type;
+ }
+
+ /**
+ * Returns a string representation for debugging purposes.
+ */
+ @Override
+ public final String toString() {
+ return Strings.toString(getClass(),
+ "operator", getOperatorType().identifier(),
+ "type", comparators.type.getSimpleName());
+ }
+
+ /**
+ * Returns an identification of this operation.
+ */
+ public abstract TemporalOperatorName getOperatorType();
+
+ /**
+ * Returns the mathematical symbol for this temporal operation.
+ *
+ * @return the mathematical symbol, or 0 if none.
+ */
+ protected char symbol() {
+ return (char) 0;
+ }
+
+ /**
+ * Evaluates the filter between two temporal objects.
+ * Both arguments given to this method shall be non-null.
+ */
+ protected boolean evaluate(T self, T other) {
+ return false;
+ }
+
+ /**
+ * Evaluates the filter between a temporal object and a period.
+ * Both arguments given to this method shall be non-null, but period begin
or end instant may be null.
+ *
+ * <p><b>Note:</b> this relationship is not defined by ISO 19108. This
method should be overridden
+ * only when an ISO 19108 extension can be easily defined, for example for
the "equal" operation.</p>
+ */
+ protected boolean evaluate(T self, Period other) {
+ return false;
+ }
+
+ /**
+ * Evaluates the filter between a period and a temporal object.
+ * Both arguments given to this method shall be non-null, but period begin
or end instant may be null.
+ * Note: the {@code self} and {@code other} argument names are chosen to
match ISO 19108 tables.
+ */
+ protected boolean evaluate(Period self, T other) {
+ return false;
+ }
+
+ /**
+ * Evaluates the filter between two periods.
+ * Both arguments given to this method shall be non-null, but period begin
or end instant may be null.
+ * Note: the {@code self} and {@code other} argument names are chosen to
match ISO 19108 tables.
+ */
+ protected abstract boolean evaluate(Period self, Period other);
+
+ /**
+ * Returns {@code true} if {@code other} is non-null and the specified
comparison evaluates to {@code true}.
+ * This is a helper function for {@code evaluate(…)} methods
implementations.
+ *
+ * @param test enumeration value such as {@link TimeMethods#BEFORE} or
{@link TimeMethods#AFTER}.
+ * @param self the object on which to invoke the method identified by
{@code test}.
+ * @param other the argument to give to the test method call, or {@code
null} if none.
+ * @return the result of performing the comparison identified by {@code
test}.
+ * @throws InvalidFilterValueException if the two objects cannot be
compared.
+ */
+ final boolean compare(final int test, final T self, final Temporal other) {
+ return (other != null) && comparators.compare(test, self, other);
+ }
+
+ /**
+ * Returns {@code true} if both arguments are non-null and the specified
comparison evaluates to {@code true}.
+ * This is a helper function for {@code evaluate(…)} methods
implementations.
+ *
+ * @param test enumeration value such as {@link TimeMethods#BEFORE} or
{@link TimeMethods#AFTER}.
+ * @param self the object on which to invoke the method identified by
{@code test}, or {@code null} if none.
+ * @param other the argument to give to the test method call, or {@code
null} if none.
+ * @return the result of performing the comparison identified by {@code
test}.
+ * @throws InvalidFilterValueException if the two objects cannot be
compared.
+ */
+ @SuppressWarnings("unchecked")
+ static boolean compare(final int test, final Temporal self, final Temporal
other) {
+ return (self != null) && (other != null) && TimeMethods.compare(test,
+ (Class) Classes.findCommonClass(self.getClass(),
other.getClass()), self, other);
+ }
+
+
+ /**
+ * Reference to a sub-class constructor.
+ *
+ * @param <T> type of temporal object.
+ */
+ @FunctionalInterface
+ static interface Factory {
+ /**
+ * Creates a new temporal operation.
+ *
+ * @param <T> type of temporal objects that the operation
will accept.
+ * @param comparators the set of methods to invoke for performing
comparisons.
+ * @return the temporal operation using the given "is before", "is
after" and "is equal" methods.
+ */
+ <T> TemporalOperation<T> create(TimeMethods<T> comparators);
+ }
+
+
+ /**
+ * The {@code "TEquals"} (=) operation. Defined by ISO 19108 as:
+ * <ul>
+ * <li>{@literal self = other}</li>
+ * <li>{@literal self.begin = other.begin AND self.end =
other.end}</li>
+ * </ul>
+ *
+ * @param <T> the base type of temporal objects.
+ */
+ static final class Equals<T> extends TemporalOperation<T> {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = -6060822291802339424L;
+
+ /** Creates a new operation. */
+ Equals(TimeMethods<T> comparators) {
+ super(comparators);
+ }
+
+ /** Identification of this operation. */
+ @Override public TemporalOperatorName getOperatorType() {
+ return TemporalOperatorName.EQUALS;
+ }
+
+ /** Symbol of this operation. */
+ @Override protected char symbol() {
+ return '=';
+ }
+
+ /** Condition defined by ISO 19108:2002 §5.2.3.5. */
+ @Override protected boolean evaluate(T self, T other) {
+ return comparators.isEqual.test(self, other);
+ }
+
+ /** Extension to ISO 19108: handle instant as a tiny period. */
+ @Override public boolean evaluate(T self, Period other) {
+ return compare(EQUAL, self, other.getBeginning()) &&
+ compare(EQUAL, self, other.getEnding());
+ }
+
+ /** Extension to ISO 19108: handle instant as a tiny period. */
+ @Override public boolean evaluate(Period self, T other) {
+ return compare(EQUAL, other, self.getBeginning()) &&
+ compare(EQUAL, other, self.getEnding());
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(Period self, Period other) {
+ return compare(EQUAL, self.getBeginning(), other.getBeginning()) &&
+ compare(EQUAL, self.getEnding(), other.getEnding());
+ }
+ }
+
+
+ /**
+ * The {@code "Before"} {@literal (<)} operation. Defined by ISO 19108 as:
+ * <ul>
+ * <li>{@literal self < other}</li>
+ * <li>{@literal self.end < other}</li>
+ * <li>{@literal self.end < other.begin}</li>
+ * </ul>
+ *
+ * @param <T> the base type of temporal objects.
+ */
+ static final class Before<T> extends TemporalOperation<T> {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = -3422629447456003982L;
+
+ /** Creates a new operation. */
+ Before(TimeMethods<T> comparators) {
+ super(comparators);
+ }
+
+ /** Identification of this operation. */
+ @Override public TemporalOperatorName getOperatorType() {
+ return TemporalOperatorName.BEFORE;
+ }
+
+ /** Symbol of this operation. */
+ @Override protected char symbol() {
+ return '<';
+ }
+
+ /** Condition defined by ISO 19108:2002 §5.2.3.5. */
+ @Override protected boolean evaluate(T self, T other) {
+ return comparators.isBefore.test(self, other);
+ }
+
+ /** Relationship not defined by ISO 19108:2006. */
+ @Override public boolean evaluate(T self, Period other) {
+ return compare(BEFORE, self, other.getBeginning());
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(Period self, T other) {
+ return compare(AFTER, other, self.getEnding());
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(Period self, Period other) {
+ return compare(BEFORE, self.getEnding(), other.getBeginning());
+ }
+ }
+
+
+ /**
+ * The {@code "After"} {@literal (>)} operation. Defined by ISO 19108 as:
+ * <ul>
+ * <li>{@literal self > other}</li>
+ * <li>{@literal self.begin > other}</li>
+ * <li>{@literal self.begin > other.end}</li>
+ * </ul>
+ *
+ * @param <T> the base type of temporal objects.
+ */
+ static final class After<T> extends TemporalOperation<T> {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = 5410476260417497682L;
+
+ /** Creates a new operation. */
+ After(TimeMethods<T> comparators) {
+ super(comparators);
+ }
+
+ /** Identification of this operation. */
+ @Override public TemporalOperatorName getOperatorType() {
+ return TemporalOperatorName.AFTER;
+ }
+
+ /** Symbol of this operation. */
+ @Override protected char symbol() {
+ return '>';
+ }
+
+ /** Condition defined by ISO 19108:2002 §5.2.3.5. */
+ @Override protected boolean evaluate(T self, T other) {
+ return comparators.isAfter.test(self, other);
+ }
+
+ /** Relationship not defined by ISO 19108:2006. */
+ @Override public boolean evaluate(T self, Period other) {
+ return compare(AFTER, self, other.getEnding());
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(Period self, T other) {
+ return compare(BEFORE, other, self.getBeginning());
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(Period self, Period other) {
+ return compare(AFTER, self.getBeginning(), other.getEnding());
+ }
+ }
+
+
+ /**
+ * The {@code "Begins"} operation. Defined by ISO 19108 as:
+ * <ul>
+ * <li>{@literal self.begin = other.begin AND self.end <
other.end}</li>
+ * </ul>
+ *
+ * @param <T> the base type of temporal objects.
+ */
+ static final class Begins<T> extends TemporalOperation<T> {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = -7880699329127762233L;
+
+ /** Creates a new operation. */
+ Begins(TimeMethods<T> comparators) {
+ super(comparators);
+ }
+
+ /** Identification of this operation. */
+ @Override public TemporalOperatorName getOperatorType() {
+ return TemporalOperatorName.BEGINS;
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(final Period self, final Period
other) {
+ return compare(EQUAL, self.getBeginning(), other.getBeginning())
&&
+ compare(BEFORE, self.getEnding(), other.getEnding());
+ }
+ }
+
+
+ /**
+ * The {@code "Ends"} operation. Defined by ISO 19108 as:
+ * <ul>
+ * <li>{@literal self.begin > other.begin AND self.end =
other.end}</li>
+ * </ul>
+ *
+ * @param <T> the base type of temporal objects.
+ */
+ static final class Ends<T> extends TemporalOperation<T> {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = -5508229966320563437L;
+
+ /** Creates a new operation. */
+ Ends(TimeMethods<T> comparators) {
+ super(comparators);
+ }
+
+ /** Identification of this operation. */
+ @Override public TemporalOperatorName getOperatorType() {
+ return TemporalOperatorName.ENDS;
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(final Period self, final Period
other) {
+ return compare(EQUAL, self.getEnding(), other.getEnding()) &&
+ compare(AFTER, self.getBeginning(), other.getBeginning());
+ }
+ }
+
+
+ /**
+ * The {@code "BegunBy"} operation. Defined by ISO 19108 as:
+ * <ul>
+ * <li>{@literal self.begin = other}</li>
+ * <li>{@literal self.begin = other.begin AND self.end >
other.end}</li>
+ * </ul>
+ *
+ * @param <T> the base type of temporal objects.
+ */
+ static final class BegunBy<T> extends TemporalOperation<T> {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = -7212413827394364384L;
+
+ /** Creates a new operation. */
+ BegunBy(TimeMethods<T> comparators) {
+ super(comparators);
+ }
+
+ /** Identification of this operation. */
+ @Override public TemporalOperatorName getOperatorType() {
+ return TemporalOperatorName.BEGUN_BY;
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(Period self, T other) {
+ return compare(EQUAL, other, self.getBeginning());
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(final Period self, final Period
other) {
+ return compare(EQUAL, self.getBeginning(), other.getBeginning()) &&
+ compare(AFTER, self.getEnding(), other.getEnding());
+ }
+ }
+
+
+ /**
+ * The {@code "EndedBy"} operation. Defined by ISO 19108 as:
+ * <ul>
+ * <li>{@literal self.end = other}</li>
+ * <li>{@literal self.begin < other.begin AND self.end =
other.end}</li>
+ * </ul>
+ *
+ * @param <T> the base type of temporal objects.
+ */
+ static final class EndedBy<T> extends TemporalOperation<T> {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = 8586566103462153666L;
+
+ /** Creates a new operation. */
+ EndedBy(TimeMethods<T> comparators) {
+ super(comparators);
+ }
+
+ /** Identification of this operation. */
+ @Override public TemporalOperatorName getOperatorType() {
+ return TemporalOperatorName.ENDED_BY;
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(final Period self, final T other) {
+ return compare(EQUAL, other, self.getEnding());
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(final Period self, final Period
other) {
+ return compare(EQUAL, self.getEnding(), other.getEnding()) &&
+ compare(BEFORE, self.getBeginning(), other.getBeginning());
+ }
+ }
+
+
+ /**
+ * The {@code "Meets"} operation. Defined by ISO 19108 as:
+ * <ul>
+ * <li>{@literal self.end = other.begin}</li>
+ * </ul>
+ *
+ * @param <T> the base type of temporal objects.
+ */
+ static final class Meets<T> extends TemporalOperation<T> {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = -3534843269384858443L;
+
+ /** Creates a new operation. */
+ Meets(TimeMethods<T> comparators) {
+ super(comparators);
+ }
+
+ /** Identification of this operation. */
+ @Override public TemporalOperatorName getOperatorType() {
+ return TemporalOperatorName.MEETS;
+ }
+
+ /** Extension to ISO 19108: handle instant as a tiny period. */
+ @Override public boolean evaluate(final T self, final T other) {
+ return comparators.isEqual.test(self, other);
+ }
+
+ /** Extension to ISO 19108: handle instant as a tiny period. */
+ @Override public boolean evaluate(final T self, final Period other) {
+ return compare(EQUAL, self, other.getBeginning());
+ }
+
+ /** Extension to ISO 19108: handle instant as a tiny period. */
+ @Override public boolean evaluate(final Period self, final T other) {
+ return compare(EQUAL, other, self.getEnding());
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(final Period self, final Period
other) {
+ return compare(EQUAL, self.getEnding(), other.getBeginning());
+ }
+ }
+
+
+ /**
+ * The {@code "MetBy"} operation. Defined by ISO 19108 as:
+ * <ul>
+ * <li>{@literal self.begin = other.end}</li>
+ * </ul>
+ *
+ * @param <T> the base type of temporal objects.
+ */
+ static final class MetBy<T> extends TemporalOperation<T> {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = 5358059498707330482L;
+
+ /** Creates a new operation. */
+ MetBy(TimeMethods<T> comparators) {
+ super(comparators);
+ }
+
+ /** Identification of this operation. */
+ @Override public TemporalOperatorName getOperatorType() {
+ return TemporalOperatorName.MET_BY;
+ }
+
+ /** Extension to ISO 19108: handle instant as a tiny period. */
+ @Override public boolean evaluate(final T self, final T other) {
+ return comparators.isEqual.test(self, other);
+ }
+
+ /** Extension to ISO 19108: handle instant as a tiny period. */
+ @Override public boolean evaluate(final T self, final Period other) {
+ return compare(EQUAL, self, other.getEnding());
+ }
+
+ /** Extension to ISO 19108: handle instant as a tiny period. */
+ @Override public boolean evaluate(final Period self, final T other) {
+ return compare(EQUAL, other, self.getBeginning());
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(final Period self, final Period
other) {
+ return compare(EQUAL, self.getBeginning(), other.getEnding());
+ }
+ }
+
+
+ /**
+ * The {@code "During"} operation. Defined by ISO 19108 as:
+ * <ul>
+ * <li>{@literal self.begin > other.begin AND self.end <
other.end}</li>
+ * </ul>
+ *
+ * @param <T> the base type of temporal objects.
+ */
+ static final class During<T> extends TemporalOperation<T> {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = -4674319635076886196L;
+
+ /** Creates a new operation. */
+ During(TimeMethods<T> comparators) {
+ super(comparators);
+ }
+
+ /** Identification of this operation. */
+ @Override public TemporalOperatorName getOperatorType() {
+ return TemporalOperatorName.DURING;
+ }
+
+ /** Symbol of this operation. */
+ @Override protected char symbol() {
+ return '⊊'; // `self` is a proper (or strict) subset of
`other`.
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(final Period self, final Period
other) {
+ return compare(AFTER, self.getBeginning(), other.getBeginning())
&&
+ compare(BEFORE, self.getEnding(), other.getEnding());
+ }
+ }
+
+
+ /**
+ * The {@code "TContains"} operation. Defined by ISO 19108 as:
+ * <ul>
+ * <li>{@literal self.begin < other AND self.end > other}</li>
+ * <li>{@literal self.begin < other.begin AND self.end >
other.end}</li>
+ * </ul>
+ *
+ * @param <T> the base type of temporal objects.
+ */
+ static final class Contains<T> extends TemporalOperation<T> {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = 9107531246948034411L;
+
+ /** Creates a new operation. */
+ Contains(TimeMethods<T> comparators) {
+ super(comparators);
+ }
+
+ /** Identification of this operation. */
+ @Override public TemporalOperatorName getOperatorType() {
+ return TemporalOperatorName.CONTAINS;
+ }
+
+ /** Symbol of this operation. */
+ @Override protected char symbol() {
+ return '⊋'; // `self` is a proper (or strict) superset of
`other`.
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(final Period self, final T other) {
+ return compare(AFTER, other, self.getBeginning()) &&
+ compare(BEFORE, other, self.getEnding());
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(final Period self, final Period
other) {
+ return compare(BEFORE, self.getBeginning(), other.getBeginning())
&&
+ compare(AFTER, self.getEnding(), other.getEnding());
+ }
+ }
+
+
+ /**
+ * The {@code "TOverlaps"} operation. Defined by ISO 19108 as:
+ * <ul>
+ * <li>{@literal self.begin < other.begin AND self.end > other.begin
AND self.end < other.end}</li>
+ * </ul>
+ *
+ * @param <T> the base type of temporal objects.
+ */
+ static final class Overlaps<T> extends TemporalOperation<T> {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = 1517443045593389773L;
+
+ /** Creates a new operation. */
+ Overlaps(TimeMethods<T> comparators) {
+ super(comparators);
+ }
+
+ /** Identification of this operation. */
+ @Override public TemporalOperatorName getOperatorType() {
+ return TemporalOperatorName.OVERLAPS;
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(final Period self, final Period
other) {
+ final Temporal selfBegin, selfEnd, otherBegin, otherEnd;
+ return ((otherBegin = other.getBeginning()) != null) &&
+ ((selfBegin = self .getBeginning()) != null) &&
compare(BEFORE, selfBegin, otherBegin) &&
+ ((selfEnd = self .getEnding()) != null) &&
compare(AFTER, selfEnd, otherBegin) &&
+ ((otherEnd = other.getEnding()) != null) &&
compare(BEFORE, selfEnd, otherEnd);
+ }
+ }
+
+
+ /**
+ * The {@code "OverlappedBy"} operation. Defined by ISO 19108 as:
+ * <ul>
+ * <li>{@literal self.begin > other.begin AND self.begin < other.end
AND self.end > other.end}</li>
+ * </ul>
+ *
+ * @param <T> the base type of temporal objects.
+ */
+ static final class OverlappedBy<T> extends TemporalOperation<T> {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = 2228673820507226463L;
+
+ /** Creates a new operation. */
+ OverlappedBy(TimeMethods<T> comparators) {
+ super(comparators);
+ }
+
+ /** Identification of this operation. */
+ @Override public TemporalOperatorName getOperatorType() {
+ return TemporalOperatorName.OVERLAPPED_BY;
+ }
+
+ /** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
+ @Override public boolean evaluate(final Period self, final Period
other) {
+ final Temporal selfBegin, selfEnd, otherBegin, otherEnd;
+ return ((selfBegin = self .getBeginning()) != null) &&
+ ((otherBegin = other.getBeginning()) != null) &&
compare(AFTER, selfBegin, otherBegin) &&
+ ((otherEnd = other.getEnding()) != null) &&
compare(BEFORE, selfBegin, otherEnd) &&
+ ((selfEnd = self .getEnding()) != null) &&
compare(AFTER, selfEnd, otherEnd);
+ }
+ }
+
+
+ /**
+ * The {@code "AnyInteracts"} filter.
+ * This is a shortcut for NOT (Before OR Meets OR MetBy OR After).
+ *
+ * @param <T> the base type of temporal objects.
+ */
+ static final class AnyInteracts<T> extends TemporalOperation<T> {
+ /** For cross-version compatibility during (de)serialization. */
+ private static final long serialVersionUID = 5972351564286442392L;
+
+ /** Creates a new operation. */
+ AnyInteracts(TimeMethods<T> comparators) {
+ super(comparators);
+ }
+
+ /** Identification of this operation. */
+ @Override public TemporalOperatorName getOperatorType() {
+ return TemporalOperatorName.ANY_INTERACTS;
+ }
+
+ /** Condition defined by OGC filter specification. */
+ @Override public boolean evaluate(final Period self, final Period
other) {
+ final Temporal selfBegin, selfEnd, otherBegin, otherEnd;
+ return ((selfBegin = self .getBeginning()) != null) &&
+ ((otherEnd = other.getEnding()) != null) &&
compare(BEFORE, selfBegin, otherEnd) &&
+ ((selfEnd = self .getEnding()) != null) &&
+ ((otherBegin = other.getBeginning()) != null) &&
compare(AFTER, selfEnd, otherBegin);
+ }
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TimeMethods.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TimeMethods.java
new file mode 100644
index 0000000000..11cc0ca6b7
--- /dev/null
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TimeMethods.java
@@ -0,0 +1,396 @@
+/*
+ * 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.Date;
+import java.time.Instant;
+import java.time.Year;
+import java.time.YearMonth;
+import java.time.MonthDay;
+import java.time.LocalTime;
+import java.time.OffsetTime;
+import java.time.OffsetDateTime;
+import java.time.DateTimeException;
+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.time.temporal.TemporalAccessor;
+import java.util.function.BiPredicate;
+import java.lang.reflect.Modifier;
+import java.io.Serializable;
+import java.io.ObjectStreamException;
+import org.apache.sis.util.privy.Strings;
+import org.apache.sis.util.resources.Errors;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.filter.InvalidFilterValueException;
+
+
+/**
+ * Provides the <i>is before</i> and <i>is after</i> operations for various
{@code java.time} objects.
+ * This class delegates to the {@code isBefore(T)} or {@code isAfter(T)}
methods of each supported classes.
+ *
+ * Instances of this classes are immutable and thread-safe.
+ * The same instance can be shared by many {@link TemporalOperation} instances.
+ *
+ * <h2>Design note about alternative approaches</h2>
+ * We do not delegate to {@link Comparable#compareTo(Object)} because the
latter method compares not only
+ * positions on the timeline, but also other properties not relevant to an "is
before" or "is after" test.
+ * We could use {@link ChronoLocalDate#timeLineOrder()} comparators instead,
but those comparators are not
+ * defined for every classes where the "is before" and "is after" methods
differ from "compare to" method.
+ * Furthermore, some temporal classes override {@code isBefore(T)} or {@code
isAfter(T)} for performance.
+ *
+ * @param <T> the base type of temporal objects, or {@code Object.class} for
any type.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ */
+class TimeMethods<T> implements Serializable {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 1075289362575825939L;
+
+ /**
+ * The type of temporal objects accepted by this set of operations.
+ */
+ public final Class<T> type;
+
+ /**
+ * Enumeration values for a test to apply.
+ *
+ * @see #compare(int, T, TemporalAccessor)
+ */
+ static final int BEFORE=1, AFTER=2, EQUAL=0;
+
+ /**
+ * Predicate to execute for testing the ordering between temporal objects.
+ * This comparison operator differs from the {@code compareTo(…)} method
in that it compares only the
+ * positions on the timeline, ignoring metadata such as the calendar used
for representing positions.
+ */
+ public final transient BiPredicate<T,T> isBefore, isAfter, isEqual;
+
+ /**
+ * Creates a new set of operators. This method is for subclasses only.
+ * For getting a {@code TimeMethods} instance, see {@link #find(Class)}.
+ */
+ private TimeMethods(final Class<T> type,
+ final BiPredicate<T,T> isBefore,
+ final BiPredicate<T,T> isAfter,
+ final BiPredicate<T,T> isEqual)
+ {
+ this.type = type;
+ this.isBefore = isBefore;
+ this.isAfter = isAfter;
+ this.isEqual = isEqual;
+ }
+
+ /**
+ * Returns whether the end point will be determined dynamically every time
that a method is invoked.
+ *
+ * @return whether the methods are determined dynamically on an
instance-by-instance basis.
+ */
+ public boolean isDynamic() {
+ return false;
+ }
+
+ /**
+ * Delegates the comparison to the method identified by the {@code test}
argument.
+ * This method is overridden in subclasses where the delegation can be
more direct.
+ *
+ * @param test {@link #BEFORE}, {@link #AFTER} or {@link #EQUAL}.
+ * @param self the object on which to invoke the method identified by
{@code test}.
+ * @param other the argument to give to the test method call.
+ * @return the result of performing the comparison identified by {@code
test}.
+ */
+ boolean delegate(final int test, final T self, final T other) {
+ final BiPredicate<T,T> p;
+ switch (test) {
+ case BEFORE: p = isBefore; break;
+ case AFTER: p = isAfter; break;
+ case EQUAL: p = isEqual; break;
+ default: throw new AssertionError(test);
+ }
+ return p.test(self, other);
+ }
+
+ /**
+ * Compares an object of class {@code <T>} with a temporal object of
unknown class.
+ * The other object is typically the beginning or ending of a period.
+ *
+ * @param test {@link #BEFORE}, {@link #AFTER} or {@link #EQUAL}.
+ * @param self the object on which to invoke the method identified by
{@code test}.
+ * @param other the argument to give to the test method call.
+ * @return the result of performing the comparison identified by {@code
test}.
+ * @throws InvalidFilterValueException if the two objects cannot be
compared.
+ */
+ @SuppressWarnings("unchecked")
+ final boolean compare(final int test, final T self, final TemporalAccessor
other) {
+ if (type.isInstance(other)) {
+ return delegate(test, self, (T) other); // Safe because of
above `isInstance(…)` check.
+ }
+ return compareAsInstants(test, accessor(self), other);
+ }
+
+ /**
+ * Compares two temporal objects of unknown class. This method needs to
check for specialized implementations
+ * before to delegate to {@link Comparable#compareTo(Object)}, because the
comparison methods on the timeline
+ * are not always the same as {@code compareTo(…)}.
+ *
+ * @param <T> base class of the objects to compare.
+ * @param test {@link #BEFORE}, {@link #AFTER} or {@link #EQUAL}.
+ * @param type base class of the {@code self} and {@code other}
arguments.
+ * @param self the object on which to invoke the method identified by
{@code test}.
+ * @param other the argument to give to the test method call.
+ * @return the result of performing the comparison identified by {@code
test}.
+ * @throws InvalidFilterValueException if the two objects cannot be
compared.
+ */
+ static <T> boolean compare(final int test, final Class<T> type, final T
self, final T other) {
+ /*
+ * The following cast is not strictly true, it should be `<? extends
T>`.
+ * However, because of the `isInstance(…)` check and because <T> is
used
+ * only as parameter type (no collection), it is okay to use it that
way.
+ */
+ final TimeMethods<? super T> tc = findSpecialized(type);
+ if (tc != null) {
+ /*
+ * Found one of the special cases listed in `INTERFACES` or
`FINAL_TYPE`.
+ * If the other type is compatible, the comparison is executed
directly.
+ * Note: the `switch` statement is equivalent to `tc.compare(test,
…)`,
+ * but is inlined because that method is never overridden in this
context.
+ */
+ if (tc.type.isInstance(other)) {
+ assert tc.type.isAssignableFrom(type) : tc; // Those types
are not necessarily equal.
+ final BiPredicate<? super T, ? super T> p;
+ switch (test) {
+ case BEFORE: p = tc.isBefore; break;
+ case AFTER: p = tc.isAfter; break;
+ case EQUAL: p = tc.isEqual; break;
+ default: throw new AssertionError(test);
+ }
+ return p.test(self, other);
+ }
+ } else if (self instanceof Comparable<?> && type.isInstance(other)) {
+ /*
+ * The type of the first operand is not a special case, but the
second operand is compatible
+ * for a call to the generic `compareTo(…)` method. This case does
not happen often, because
+ * not many `java.time` classes have no "is before" or "is after"
operations.
+ * Some examples are `Month` and `DayOfWeek`.
+ */
+ @SuppressWarnings("unchecked") // Safe because
verification done by `isInstance(…)`.
+ final int c = ((Comparable) self).compareTo(other);
+ switch (test) {
+ case BEFORE: return c < 0;
+ case AFTER: return c > 0;
+ case EQUAL: return c == 0;
+ default: throw new AssertionError(test);
+ }
+ }
+ /*
+ * If we reach this point, the two operands are of different classes
and we cannot compare them directly.
+ * Try to compare the two operands as instants on the timeline.
+ */
+ return compareAsInstants(test, accessor(self), accessor(other));
+ }
+
+ /**
+ * Returns the given object as a temporal accessor.
+ */
+ private static TemporalAccessor accessor(final Object value) {
+ if (value instanceof TemporalAccessor) {
+ return (TemporalAccessor) value;
+ } else if (value instanceof Date) {
+ return ((Date) value).toInstant(); // Overridden in `Date`
subclasses.
+ } else {
+ throw new InvalidFilterValueException(Errors.format(
+ Errors.Keys.CannotCompareInstanceOf_2, value.getClass(),
TemporalAccessor.class));
+ }
+ }
+
+ /**
+ * Compares two temporal objects as instants.
+ * This is a last-resort fallback, when objects cannot be compared by
their own methods.
+ *
+ * @param test {@link #BEFORE}, {@link #AFTER} or {@link #EQUAL}.
+ * @param self the object on which to invoke the method identified by
{@code test}.
+ * @param other the argument to give to the test method call.
+ * @return the result of performing the comparison identified by {@code
test}.
+ * @throws InvalidFilterValueException if the two objects cannot be
compared.
+ */
+ private static boolean compareAsInstants(final int test, final
TemporalAccessor self, final TemporalAccessor other) {
+ try {
+ long t1 = self.getLong(ChronoField.INSTANT_SECONDS);
+ long t2 = other.getLong(ChronoField.INSTANT_SECONDS);
+ if (t1 == t2) {
+ t1 = self.getLong(ChronoField.NANO_OF_SECOND); // Should
be present according Javadoc.
+ t2 = other.getLong(ChronoField.NANO_OF_SECOND);
+ if (t1 == t2) {
+ return test == EQUAL;
+ }
+ }
+ return test == ((t1 < t2) ? BEFORE : AFTER);
+ } catch (DateTimeException | ArithmeticException e) {
+ throw new InvalidFilterValueException(Errors.format(
+ Errors.Keys.CannotCompareInstanceOf_2, self.getClass(),
other.getClass()), e);
+ }
+ }
+
+ /**
+ * Returns the set of methods that can be invoked on instances of the
given type, or {@code null} if none.
+ * This method returns only one of the methods defined in {@link
#FINAL_TYPES} or {@link #INTERFACES}.
+ * It shall not try to create fallbacks.
+ *
+ * @param <T> compile-time value of the {@code type} argument.
+ * @param type the type of temporal object for which to get specialized
methods.
+ * @return set of specialized methods for the given object type, or {@code
null} if none.
+ */
+ @SuppressWarnings("unchecked")
+ private static <T> TimeMethods<? super T> findSpecialized(final Class<T>
type) {
+ { // Block for keeping `tc` in local scope.
+ TimeMethods<?> tc = FINAL_TYPES.get(type);
+ if (tc != null) {
+ assert tc.type == type : tc;
+ return (TimeMethods<T>) tc; // Safe because of
`==` checks.
+ }
+ }
+ for (TimeMethods<?> tc : INTERFACES) {
+ if (tc.type.isAssignableFrom(type)) {
+ return (TimeMethods<? super T>) tc; // Safe because of
`isAssignableFrom(…)` checks.
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the set of methods that can be invoked on instances of the
given type.
+ *
+ * @param <T> compile-time value of the {@code type} argument.
+ * @param type the type of temporal object for which to get specialized
methods.
+ * @return set of comparison methods for the given object type.
+ */
+ @SuppressWarnings("unchecked") // For (Comparable) casts.
+ public static <T> TimeMethods<? super T> find(final Class<T> type) {
+ final TimeMethods<? super T> tc = findSpecialized(type);
+ if (tc != null) {
+ return tc;
+ }
+ if (Modifier.isFinal(type.getModifiers())) {
+ if (Comparable.class.isAssignableFrom(type)) {
+ return new TimeMethods<>(type,
+ (self, other) -> ((Comparable) self).compareTo(other)
< 0,
+ (self, other) -> ((Comparable) self).compareTo(other)
> 0,
+ (self, other) -> ((Comparable) self).compareTo(other)
== 0);
+ } else {
+ throw new
InvalidFilterValueException(Errors.format(Errors.Keys.CannotCompareInstanceOf_2,
type, type));
+ }
+ } else {
+ return fallback(type);
+ }
+ }
+
+ /**
+ * Returns the last-resort fallback when the type of temporal objects
cannot be determined in advance.
+ *
+ * @param <T> compile-time value of the {@code type} argument.
+ * @param type the type of temporal object for which to get the
last-resource fallback methods.
+ * @return set of last-resort comparison methods for the given object type.
+ */
+ private static <T> TimeMethods<? super T> fallback(final Class<T> type) {
+ return new TimeMethods<>(type,
+ (self, other) -> compare(BEFORE, type, self, other),
+ (self, other) -> compare(AFTER, type, self, other),
+ (self, other) -> compare(EQUAL, type, self, other))
+ {
+ @Override public boolean isDynamic() {
+ return true;
+ }
+ @Override boolean delegate(final int test, final T self, final T
other) {
+ return compare(test, type, self, other);
+ }
+ };
+ }
+
+ /**
+ * Returns the unique instance for the type after deserialization.
+ * This is needed for avoiding to serialize the lambda functions.
+ *
+ * @return the object to use after deserialization.
+ * @throws ObjectStreamException if the serialized object contains invalid
data.
+ */
+ private Object readResolve() throws ObjectStreamException {
+ return find(type);
+ }
+
+ /**
+ * Operators for all supported temporal types that are interfaces or
non-final classes.
+ * Those types need to be checked with {@link
Class#isAssignableFrom(Class)} in iteration order.
+ */
+ @SuppressWarnings({"rawtypes", "unchecked"}) // For `Chrono*`
interfaces, because they are parameterized.
+ private static final TimeMethods<?>[] INTERFACES = {
+ new TimeMethods<>(ChronoZonedDateTime.class,
ChronoZonedDateTime::isBefore, ChronoZonedDateTime::isAfter,
ChronoZonedDateTime::isEqual),
+ new TimeMethods<>(ChronoLocalDateTime.class,
ChronoLocalDateTime::isBefore, ChronoLocalDateTime::isAfter,
ChronoLocalDateTime::isEqual),
+ new TimeMethods<>( ChronoLocalDate.class,
ChronoLocalDate::isBefore, ChronoLocalDate::isAfter,
ChronoLocalDate::isEqual),
+ new TimeMethods<>( Date.class, Date::
before, Date:: after, Date::equals)
+ };
+
+ /*
+ * No operation on numbers for now. We could revisit this policy in a
future version if we
+ * allow the temporal function to have a CRS and to operate on temporal
coordinate values.
+ */
+
+ /**
+ * Operators for all supported temporal types for which there is no need
to check for subclasses.
+ * Those classes are usually final, except when wanting to intentionally
ignore all subclasses.
+ * Those types should be tested before {@link #INTERFACES} because this
check is quick.
+ *
+ * <h4>Implementation note</h4>
+ * {@link Year}, {@link YearMonth}, {@link MonthDay}, {@link LocalTime}
and {@link Instant}
+ * could be replaced by {@link Comparable}. We nevertheless keep the
specialized classes in
+ * case the implementations change in the future, and also for performance
reason, because
+ * the code working on generic {@link Comparable} needs to check for
special cases again.
+ */
+ private static final Map<Class<?>, TimeMethods<?>> FINAL_TYPES =
Map.ofEntries(
+ entry(new TimeMethods<>(OffsetDateTime.class,
OffsetDateTime::isBefore, OffsetDateTime::isAfter, OffsetDateTime::isEqual)),
+ entry(new TimeMethods<>( OffsetTime.class,
OffsetTime::isBefore, OffsetTime::isAfter, OffsetTime::isEqual)),
+ entry(new TimeMethods<>( LocalTime.class,
LocalTime::isBefore, LocalTime::isAfter, LocalTime::equals)),
+ entry(new TimeMethods<>( Year.class,
Year::isBefore, Year::isAfter, Year::equals)),
+ entry(new TimeMethods<>( YearMonth.class,
YearMonth::isBefore, YearMonth::isAfter, YearMonth::equals)),
+ entry(new TimeMethods<>( MonthDay.class,
MonthDay::isBefore, MonthDay::isAfter, MonthDay::equals)),
+ entry(new TimeMethods<>( Instant.class,
Instant::isBefore, Instant::isAfter, Instant::equals)),
+ entry(fallback(Temporal.class)), // Frequently declared type.
Intentionally no "instance of" checks.
+ entry(fallback(Object.class))); // Not a final class, but to be
used when the declared type is Object.
+
+ /**
+ * Helper method for adding entries to the {@link #FINAL_TYPES} map.
+ * Shall be used only for final classes.
+ */
+ private static Map.Entry<Class<?>, TimeMethods<?>> entry(final
TimeMethods<?> op) {
+ return Map.entry(op.type, op);
+ }
+
+ /**
+ * Returns a string representation of this set of operations for debugging
purposes.
+ *
+ * @return a string representation for debugging purposes.
+ */
+ @Override
+ public String toString() {
+ return Strings.toString(TimeMethods.class, "type",
type.getSimpleName());
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/TemporalFilterTest.java
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/TemporalFilterTest.java
index 249639181b..b349e62672 100644
---
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/TemporalFilterTest.java
+++
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/TemporalFilterTest.java
@@ -16,16 +16,18 @@
*/
package org.apache.sis.filter;
+import java.time.Instant;
+import org.apache.sis.geometry.WraparoundMethod;
import static org.apache.sis.util.privy.Constants.MILLISECONDS_PER_DAY;
// Test dependencies
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.apache.sis.test.TestCase;
-import org.apache.sis.test.TestUtilities;
import static org.apache.sis.test.Assertions.assertSerializedEquals;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.temporal.Period;
import org.opengis.feature.Feature;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.TemporalOperator;
@@ -42,7 +44,7 @@ public final class TemporalFilterTest extends TestCase {
/**
* The factory to use for creating the objects to test.
*/
- private final FilterFactory<Feature,Object,Object> factory;
+ private FilterFactory<Feature, Object, ? super Period> factory;
/**
* The filter to test. This field shall be assigned by each {@code
testFoo()} method by invoking
@@ -63,8 +65,8 @@ public final class TemporalFilterTest extends TestCase {
factory = DefaultFilterFactory.forFeatures();
expression1 = new PeriodLiteral();
expression2 = new PeriodLiteral();
- expression1.begin = expression2.begin = TestUtilities.date("2000-01-01
09:00:00").getTime();
- expression1.end = expression2.end = TestUtilities.date("2000-01-05
10:00:00").getTime();
+ expression1.begin = expression2.begin =
Instant.parse("2000-01-01T09:00:00Z").toEpochMilli();
+ expression1.end = expression2.end =
Instant.parse("2000-01-05T10:00:00Z").toEpochMilli();
}
/**
@@ -126,6 +128,8 @@ public final class TemporalFilterTest extends TestCase {
/**
* Tests "After" (construction, evaluation, serialization, equality).
+ *
+ * @see #testOnPeriods()
*/
@Test
public void testAfter() {
@@ -344,4 +348,15 @@ public final class TemporalFilterTest extends TestCase {
expression1.end += 2;
assertTrue(evaluate());
}
+
+ /**
+ * Re-test an arbitrary operation, but using a different factory.
+ * This test uses a factory specialized for the {@link Period} type
+ * in order to test a different code path.
+ */
+ @Test
+ public void testOnPeriods() {
+ factory = new DefaultFilterFactory.Features<>(Object.class,
Period.class, WraparoundMethod.NONE);
+ testAfter();
+ }
}
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
index 4dcfbc663c..a2690ff1a3 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/extent/DefaultTemporalExtent.java
@@ -169,7 +169,7 @@ public class DefaultTemporalExtent extends ISOMetadata
implements TemporalExtent
static Instant getBound(final TemporalPrimitive extent, final boolean
begin) {
if (extent instanceof Period) {
var p = (Period) extent;
- return begin ? p.getBeginning() : p.getEnding();
+ return TemporalDate.toInstant(begin ? p.getBeginning() :
p.getEnding(), null);
}
return null;
}
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriod.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriod.java
index d79b3a65e6..ee08276f1e 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriod.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/DefaultPeriod.java
@@ -17,12 +17,12 @@
package org.apache.sis.pending.temporal;
import java.util.Objects;
-import java.time.Instant;
+import java.time.temporal.Temporal;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import java.time.Duration;
import java.time.temporal.TemporalAmount;
import org.opengis.temporal.Period;
-import org.apache.sis.pending.jdk.JDK23;
/**
@@ -33,27 +33,27 @@ import org.apache.sis.pending.jdk.JDK23;
*/
final class DefaultPeriod extends Primitive implements Period {
/** Bounds making this period. */
- private final Instant beginning, ending;
+ private final Temporal beginning, ending;
/** Creates a new period with the given bounds. */
- DefaultPeriod(final Instant beginning, final Instant ending) {
+ DefaultPeriod(final Temporal beginning, final Temporal ending) {
this.beginning = beginning;
this.ending = ending;
}
/** The beginning instant at which this period starts. */
- @Override public Instant getBeginning() {
+ @Override public Temporal getBeginning() {
return beginning;
}
/** The ending instant at which this period ends. */
- @Override public Instant getEnding() {
+ @Override public Temporal getEnding() {
return ending;
}
/** Duration of this temporal geometric primitive. */
@Override public TemporalAmount length() {
- return (beginning != null && ending != null) ? JDK23.until(beginning,
ending) : null;
+ return (beginning != null && ending != null) ?
Duration.between(beginning, ending) : null;
}
/** String representation. */
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/TemporalUtilities.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/TemporalUtilities.java
index baedbaaaed..eeced6a22a 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/TemporalUtilities.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/temporal/TemporalUtilities.java
@@ -46,21 +46,10 @@ public final class TemporalUtilities {
* @param time the date for which to create instant, or {@code null}.
* @return the instant, or {@code null} if the given time was null.
*/
- public static TemporalPrimitive createInstant(final Instant time) {
+ public static TemporalPrimitive createInstant(final Temporal time) {
return (time == null) ? null : new DefaultPeriod(time, time);
}
- /**
- * Creates a period for the given begin and end instant.
- *
- * @param begin the begin instant (inclusive), or {@code null}.
- * @param end the end instant (inclusive), or {@code null}.
- * @return the period, or {@code null} if both arguments are null.
- */
- public static TemporalPrimitive createPeriod(final Instant begin, final
Instant end) {
- return (begin == null && end == null) ? null : new
DefaultPeriod(begin, end);
- }
-
/**
* Creates a period for the given begin and end instant.
*
@@ -71,8 +60,7 @@ public final class TemporalUtilities {
* @todo Needs to avoid assuming UTC timezone.
*/
public static TemporalPrimitive createPeriod(final Temporal begin, final
Temporal end) {
- return createPeriod(TemporalDate.toInstant(begin, ZoneOffset.UTC),
- TemporalDate.toInstant(end, ZoneOffset.UTC));
+ return (begin == null && end == null) ? null : new
DefaultPeriod(begin, end);
}
/**
@@ -86,9 +74,9 @@ public final class TemporalUtilities {
public static Period getPeriod(final TemporalPrimitive time) {
if (time instanceof Period) {
var p = (Period) time;
- final Instant begin = p.getBeginning();
+ final Temporal begin = p.getBeginning();
if (begin != null) {
- final Instant end = p.getEnding();
+ final Temporal end = p.getEnding();
if (end != null && !begin.equals(end)) {
return p;
}
@@ -108,8 +96,8 @@ public final class TemporalUtilities {
public static Instant getInstant(final TemporalPrimitive time) {
if (time instanceof Period) {
var p = (Period) time;
- final Instant begin = p.getBeginning();
- final Instant end = p.getEnding();
+ final Instant begin = TemporalDate.toInstant(p.getBeginning(),
ZoneOffset.UTC);
+ final Instant end = TemporalDate.toInstant(p.getEnding(),
ZoneOffset.UTC);
if (end == null) {
return begin;
}
@@ -130,7 +118,7 @@ public final class TemporalUtilities {
public static Date getAnyDate(final TemporalPrimitive time) {
if (time instanceof Period) {
var p = (Period) time;
- Instant instant;
+ Temporal instant;
if ((instant = p.getEnding()) != null || (instant =
p.getBeginning()) != null) {
return TemporalDate.toDate(instant);
}
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TimePeriodBound.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TimePeriodBound.java
index da5c230f3f..edfe70d45d 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TimePeriodBound.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TimePeriodBound.java
@@ -16,12 +16,14 @@
*/
package org.apache.sis.xml.bind.gml;
-import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.temporal.Temporal;
import javax.xml.datatype.XMLGregorianCalendar;
import jakarta.xml.bind.annotation.XmlValue;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlTransient;
+import org.apache.sis.util.privy.TemporalDate;
/**
@@ -98,8 +100,8 @@ public abstract class TimePeriodBound {
* @param instant the instant of the new bound, or {@code null}.
* @param indeterminate the value to give to {@link
#indeterminatePosition} if the date is null.
*/
- GML3(final Instant instant, final String indeterminate) {
- value = TimeInstant.toXML(instant);
+ GML3(final Temporal instant, final String indeterminate) {
+ value = TimeInstant.toXML(TemporalDate.toInstant(instant,
ZoneOffset.UTC));
if (value == null) {
indeterminatePosition = indeterminate;
}
@@ -154,8 +156,8 @@ public abstract class TimePeriodBound {
*
* @param instant The instant of the new bound, or {@code null}.
*/
- GML2(final Instant instant) {
- timeInstant = new TimeInstant(instant);
+ GML2(final Temporal instant) {
+ timeInstant = new TimeInstant(TemporalDate.toInstant(instant,
ZoneOffset.UTC));
}
/**
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
index 51ebb99c78..99648a7ba6 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java
@@ -146,7 +146,9 @@ public class DefaultTemporalDatum extends AbstractDatum
implements TemporalDatum
* @param properties the properties to be given to the identified object.
* @param origin the date and time origin of this temporal datum.
*
- * @see
org.apache.sis.referencing.factory.GeodeticObjectFactory#createTemporalDatum(Map,
Date)
+ * @see
org.apache.sis.referencing.factory.GeodeticObjectFactory#createTemporalDatum(Map,
Temporal)
+ *
+ * @since 1.5
*/
public DefaultTemporalDatum(final Map<String,?> properties, final Temporal
origin) {
super(properties);
diff --git
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Epoch.java
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Epoch.java
index 558fff3fef..5be71863b9 100644
---
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Epoch.java
+++
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/Epoch.java
@@ -69,9 +69,9 @@ public final class Epoch extends FormattableObject {
epoch = ((YearMonth) epoch).atDay(1);
fractionDigits = 2;
} else if (epoch.isSupported(ChronoField.NANO_OF_DAY)) {
- day = epoch.getLong(ChronoField.NANO_OF_DAY);
- day /= (double) Constants.NANOSECONDS_PER_DAY;
- fractionDigits = (epoch.get(ChronoField.NANO_OF_SECOND) != 0)
? 16 : 8;
+ final long nano = epoch.getLong(ChronoField.NANO_OF_DAY);
+ day = nano / (double) Constants.NANOSECONDS_PER_DAY;
+ fractionDigits = ((nano % Constants.NANOS_PER_SECOND) != 0) ?
16 : 8;
}
day += epoch.get(ChronoField.DAY_OF_YEAR) - 1;
value = year + day / Year.of(year).length();
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java
index c14ee6774a..03b1ea2bd4 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/Classes.java
@@ -539,9 +539,9 @@ next: for (final Class<?> candidate : candidates) {
}
/**
- * Returns the most specific class implemented by the objects in the given
collection.
- * If there is more than one specialized class, returns their {@linkplain
#findCommonClass
- * most specific common super class}.
+ * Returns the most specific class of the objects in the given collection.
+ * If there is more than one specialized class,
+ * returns their {@linkplain #findCommonClass most specific common super
class}.
*
* <p>This method searches for classes only, not interfaces.</p>
*
@@ -653,7 +653,7 @@ next: for (final Class<?> candidate : candidates) {
return Collections.emptySet();
}
interfaces.retainAll(buffer);
- for (Iterator<Class<?>> it=interfaces.iterator(); it.hasNext();) {
+ for (Iterator<Class<?>> it = interfaces.iterator(); it.hasNext();) {
final Class<?> candidate = it.next();
buffer.clear(); // Safe because the buffer cannot be
Collections.EMPTY_SET at this point.
getInterfaceSet(candidate, buffer);
@@ -729,29 +729,25 @@ cmp: for (final Class<?> c : c1) {
* <th>{@code getSimpleName()}</th>
* <th>{@code getCanonicalName()}</th>
* <th>{@code getShortName()}</th>
- * </tr>
- * <tr>
+ * </tr><tr>
* <td>{@link String}</td>
* <td>{@code "java.lang.String"}</td>
* <td>{@code "String"}</td>
* <td>{@code "java.lang.String"}</td>
* <td>{@code "String"}</td>
- * </tr>
- * <tr>
+ * </tr><tr>
* <td>{@code double[]}</td>
* <td>{@code "[D"}</td>
* <td>{@code "double[]"}</td>
* <td>{@code "double[]"}</td>
* <td>{@code "double[]"}</td>
- * </tr>
- * <tr>
+ * </tr><tr>
* <td>{@link java.awt.geom.Point2D.Double}</td>
* <td>{@code "java.awt.geom.Point2D$Double"}</td>
* <td>{@code "Double"}</td>
* <td>{@code "java.awt.geom.Point2D.Double"}</td>
* <td>{@code "Point2D.Double"}</td>
- * </tr>
- * <tr>
+ * </tr><tr>
* <td>Anonymous {@link Comparable}</td>
* <td>{@code "com.mycompany.myclass$1"}</td>
* <td>{@code ""}</td>
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java
index 77f10593fa..3ebaf7cd64 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.java
@@ -193,6 +193,11 @@ public class Errors extends IndexedResourceBundle {
*/
public static final short CanNotWriteFile_2 = 27;
+ /**
+ * Cannot compare instance of ‘{0}’ with ‘{1}’.
+ */
+ public static final short CannotCompareInstanceOf_2 = 205;
+
/**
* Circular reference.
*/
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties
index cf1ed4b216..955faafe3e 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors.properties
@@ -32,6 +32,7 @@ CanNotAddToExclusiveSet_2 = No element can be added
to this set because
CanNotAssign_2 = Cannot assign \u201c{1}\u201d to
\u201c{0}\u201d.
CanNotAssignUnitToDimension_2 = Cannot assign units \u201c{1}\u201d to
dimension \u201c{0}\u201d.
CanNotAssignUnitToVariable_2 = Cannot assign units \u201c{1}\u201d to
variable \u201c{0}\u201d.
+CannotCompareInstanceOf_2 = Cannot compare instance of \u2018{0}\u2019
with \u2018{1}\u2019.
CanNotConnectTo_1 = Cannot connect to \u201c{0}\u201d.
CanNotConvertFromType_2 = Cannot convert from type \u2018{0}\u2019
to type \u2018{1}\u2019.
CanNotConvertValue_2 = Cannot convert value \u201c{0}\u201d to
type \u2018{1}\u2019.
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
index 46b4e9cdfb..cfc7abb465 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Errors_fr.properties
@@ -29,6 +29,7 @@ CanNotAddToExclusiveSet_2 = Aucun \u00e9l\u00e9ment
ne peut \u00eatre aj
CanNotAssign_2 = Ne peut pas assigner
\u00ab\u202f{1}\u202f\u00bb \u00e0 \u00ab\u202f{0}\u202f\u00bb.
CanNotAssignUnitToDimension_2 = Ne peut pas assigner les unit\u00e9s
\u00ab\u202f{1}\u202f\u00bb \u00e0 la dimension \u00ab\u202f{0}\u202f\u00bb.
CanNotAssignUnitToVariable_2 = Ne peut pas assigner les unit\u00e9s
\u00ab\u202f{1}\u202f\u00bb \u00e0 la variable \u00ab\u202f{0}\u202f\u00bb.
+CannotCompareInstanceOf_2 = Ne peut pas comparer des instances de
\u2018{0}\u2019 avec \u2018{1}\u2019.
CanNotConnectTo_1 = Ne peut pas se connecter \u00e0
\u00ab\u202f{0}\u202f\u00bb.
CanNotConvertFromType_2 = Ne peut pas convertir du type
\u2018{0}\u2019 vers le type \u2018{1}\u2019.
CanNotConvertValue_2 = La valeur \u00ab\u202f{0}\u202f\u00bb ne
peut pas \u00eatre convertie vers le type \u2018{1}\u2019.
diff --git a/geoapi/snapshot b/geoapi/snapshot
index ccc41ee613..dd12a26da8 160000
--- a/geoapi/snapshot
+++ b/geoapi/snapshot
@@ -1 +1 @@
-Subproject commit ccc41ee613b7451366a796fada91c1f74f8f7168
+Subproject commit dd12a26da8121f0af6d324e33195912f21e16969