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 c1c5e12061 Allow the association of GML identifiers to temporal
object. It requires making temporal objects lenient comparable.
c1c5e12061 is described below
commit c1c5e1206100ba87fd080ca3643627170c028d93
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Fri Jun 14 14:33:02 2024 +0200
Allow the association of GML identifiers to temporal object.
It requires making temporal objects lenient comparable.
---
.../org/apache/sis/filter/TemporalOperation.java | 184 ++++++++++++---------
.../org/apache/sis/temporal/DefaultInstant.java | 39 ++++-
.../org/apache/sis/temporal/DefaultPeriod.java | 34 +++-
.../org/apache/sis/temporal/GeneralDuration.java | 21 ++-
.../org/apache/sis/temporal/TemporalObject.java | 89 ++++++++++
.../main/org/apache/sis/temporal/TimeMethods.java | 20 ++-
.../apache/sis/xml/bind/IdentifierMapAdapter.java | 1 +
.../sis/metadata/iso/extent/DefaultExtentTest.java | 3 +-
8 files changed, 297 insertions(+), 94 deletions(-)
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
index 1e6ae3ed5d..a59b174dc6 100644
---
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
@@ -16,10 +16,10 @@
*/
package org.apache.sis.filter;
+import java.util.Optional;
import java.io.Serializable;
import java.time.DateTimeException;
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 org.apache.sis.temporal.TimeMethods;
@@ -29,7 +29,9 @@ import static org.apache.sis.temporal.TimeMethods.EQUAL;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.temporal.Period;
+import org.opengis.temporal.Instant;
import org.opengis.filter.TemporalOperatorName;
+import org.opengis.temporal.IndeterminateValue;
/**
@@ -174,32 +176,6 @@ abstract class TemporalOperation<T> implements
Serializable {
*/
protected abstract boolean evaluate(Period self, Period other);
- /**
- * Returns the beginning of the given period, or {@code null} if
indeterminate.
- *
- * @param p the period from which to get the beginning.
- * @return beginning of the given period, or {@code null} if indeterminate.
- *
- * @todo Handle "before" and "after" indeterminate values.
- */
- static Temporal getBeginning(final Period p) {
- final var t = p.getBeginning();
- return (t != null) ? t.getPosition() : null;
- }
-
- /**
- * Returns the ending of the given period, or {@code null} if
indeterminate.
- *
- * @param p the period from which to get the ending.
- * @return ending of the given period, or {@code null} if indeterminate.
- *
- * @todo Handle "before" and "after" indeterminate values.
- */
- static Temporal getEnding(final Period p) {
- final var t = p.getEnding();
- return (t != null) ? t.getPosition() : null;
- }
-
/**
* 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.
@@ -210,8 +186,29 @@ abstract class TemporalOperation<T> implements
Serializable {
* @return the result of performing the comparison identified by {@code
test}.
* @throws DateTimeException if the two objects cannot be compared.
*/
- protected final boolean compare(final int test, final T self, final
Temporal other) {
- return (other != null) && comparators.compare(test, self, other);
+ protected final boolean compare(final int test, final T self, final
Instant other) {
+ if (other != null) {
+ final Temporal position;
+ final Optional<IndeterminateValue> p =
other.getIndeterminatePosition();
+ if (p.isPresent()) {
+ if (p.get() == IndeterminateValue.NOW) {
+ position = comparators.now();
+ } else {
+ switch (test) {
+ case BEFORE: if (p.get() != IndeterminateValue.AFTER)
return false; else break;
+ case AFTER: if (p.get() != IndeterminateValue.BEFORE)
return false; else break;
+ default: return false;
+ }
+ position = other.getPosition();
+ }
+ } else {
+ position = other.getPosition();
+ }
+ if (position != null) {
+ return comparators.compare(test, self, position);
+ }
+ }
+ return false;
}
/**
@@ -225,9 +222,34 @@ abstract class TemporalOperation<T> implements
Serializable {
* @throws DateTimeException if the two objects cannot be compared.
*/
@SuppressWarnings("unchecked")
- protected 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);
+ protected static boolean compare(final int test, final Instant self, final
Instant other) {
+ if (self == null || other == null) {
+ return false;
+ }
+ final IndeterminateValue p1 =
self.getIndeterminatePosition().orElse(null);
+ final IndeterminateValue p2 =
other.getIndeterminatePosition().orElse(null);
+ if (p1 != null || p2 != null) {
+ if (p1 == p2) {
+ return (test == EQUAL) && (p1 == IndeterminateValue.NOW);
+ }
+ switch (test) {
+ case BEFORE: if (isAmbiguous(p1, IndeterminateValue.BEFORE) ||
isAmbiguous(p2, IndeterminateValue.AFTER)) return false; else break;
+ case AFTER: if (isAmbiguous(p1, IndeterminateValue.AFTER) ||
isAmbiguous(p2, IndeterminateValue.BEFORE)) return false; else break;
+ default: return false;
+ }
+ }
+ return TimeMethods.compareAny(test, self.getPosition(),
other.getPosition());
+ }
+
+ /**
+ * Returns {@code true} if using the {@code p} value would be ambiguous.
+ *
+ * @param p the indeterminate value to test.
+ * @param required the required value for a non-ambiguous comparison.
+ * @return whether using the given value would be ambiguous.
+ */
+ private static boolean isAmbiguous(final IndeterminateValue p, final
IndeterminateValue required) {
+ return (p != null) && (p != IndeterminateValue.NOW) && (p != required);
}
@@ -284,20 +306,20 @@ abstract class TemporalOperation<T> implements
Serializable {
/** Extension to ISO 19108: handle instant as a tiny period. */
@Override public boolean evaluate(T self, Period other) {
- return compare(EQUAL, self, getBeginning(other)) &&
- compare(EQUAL, self, getEnding(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, getBeginning(self)) &&
- compare(EQUAL, other, getEnding(self));
+ 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, getBeginning(self), getBeginning(other)) &&
- compare(EQUAL, getEnding(self), getEnding(other));
+ return compare(EQUAL, self.getBeginning(), other.getBeginning()) &&
+ compare(EQUAL, self.getEnding(), other.getEnding());
}
}
@@ -338,17 +360,17 @@ abstract class TemporalOperation<T> implements
Serializable {
/** Relationship not defined by ISO 19108:2006. */
@Override public boolean evaluate(T self, Period other) {
- return compare(BEFORE, self, getBeginning(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, getEnding(self));
+ 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, getEnding(self), getBeginning(other));
+ return compare(BEFORE, self.getEnding(), other.getBeginning());
}
}
@@ -389,17 +411,17 @@ abstract class TemporalOperation<T> implements
Serializable {
/** Relationship not defined by ISO 19108:2006. */
@Override public boolean evaluate(T self, Period other) {
- return compare(AFTER, self, getEnding(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, getBeginning(self));
+ 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, getBeginning(self), getEnding(other));
+ return compare(AFTER, self.getBeginning(), other.getEnding());
}
}
@@ -428,8 +450,8 @@ abstract class TemporalOperation<T> implements Serializable
{
/** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
@Override public boolean evaluate(final Period self, final Period
other) {
- return compare(EQUAL, getBeginning(self), getBeginning(other)) &&
- compare(BEFORE, getEnding(self), getEnding(other));
+ return compare(EQUAL, self.getBeginning(), other.getBeginning())
&&
+ compare(BEFORE, self.getEnding(), other.getEnding());
}
}
@@ -458,8 +480,8 @@ abstract class TemporalOperation<T> implements Serializable
{
/** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
@Override public boolean evaluate(final Period self, final Period
other) {
- return compare(EQUAL, getEnding(self), getEnding(other)) &&
- compare(AFTER, getBeginning(self), getBeginning(other));
+ return compare(EQUAL, self.getEnding(), other.getEnding()) &&
+ compare(AFTER, self.getBeginning(), other.getBeginning());
}
}
@@ -489,13 +511,13 @@ abstract class TemporalOperation<T> implements
Serializable {
/** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
@Override public boolean evaluate(Period self, T other) {
- return compare(EQUAL, other, getBeginning(self));
+ 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, getBeginning(self), getBeginning(other)) &&
- compare(AFTER, getEnding(self), getEnding(other));
+ return compare(EQUAL, self.getBeginning(), other.getBeginning()) &&
+ compare(AFTER, self.getEnding(), other.getEnding());
}
}
@@ -525,13 +547,13 @@ abstract class TemporalOperation<T> implements
Serializable {
/** 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, getEnding(self));
+ 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, getEnding(self), getEnding(other)) &&
- compare(BEFORE, getBeginning(self), getBeginning(other));
+ return compare(EQUAL, self.getEnding(), other.getEnding()) &&
+ compare(BEFORE, self.getBeginning(), other.getBeginning());
}
}
@@ -565,17 +587,17 @@ abstract class TemporalOperation<T> implements
Serializable {
/** Extension to ISO 19108: handle instant as a tiny period. */
@Override public boolean evaluate(final T self, final Period other) {
- return compare(EQUAL, self, getBeginning(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, getEnding(self));
+ 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, getEnding(self), getBeginning(other));
+ return compare(EQUAL, self.getEnding(), other.getBeginning());
}
}
@@ -609,17 +631,17 @@ abstract class TemporalOperation<T> implements
Serializable {
/** Extension to ISO 19108: handle instant as a tiny period. */
@Override public boolean evaluate(final T self, final Period other) {
- return compare(EQUAL, self, getEnding(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, getBeginning(self));
+ 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, getBeginning(self), getEnding(other));
+ return compare(EQUAL, self.getBeginning(), other.getEnding());
}
}
@@ -653,8 +675,8 @@ abstract class TemporalOperation<T> implements Serializable
{
/** Condition defined by ISO 19108:2006 (corrigendum) §5.2.3.5. */
@Override public boolean evaluate(final Period self, final Period
other) {
- return compare(AFTER, getBeginning(self), getBeginning(other)) &&
- compare(BEFORE, getEnding(self), getEnding(other));
+ return compare(AFTER, self.getBeginning(), other.getBeginning())
&&
+ compare(BEFORE, self.getEnding(), other.getEnding());
}
}
@@ -689,14 +711,14 @@ abstract class TemporalOperation<T> implements
Serializable {
/** 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, getBeginning(self)) &&
- compare(BEFORE, other, getEnding(self));
+ 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, getBeginning(self), getBeginning(other)) &&
- compare(AFTER, getEnding(self), getEnding(other));
+ return compare(BEFORE, self.getBeginning(), other.getBeginning())
&&
+ compare(AFTER, self.getEnding(), other.getEnding());
}
}
@@ -725,11 +747,11 @@ abstract class TemporalOperation<T> implements
Serializable {
/** 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 = getBeginning(other)) != null) &&
- ((selfBegin = getBeginning(self)) != null) &&
compare(BEFORE, selfBegin, otherBegin) &&
- ((selfEnd = getEnding (self)) != null) &&
compare(AFTER, selfEnd, otherBegin) &&
- ((otherEnd = getEnding (other)) != null) &&
compare(BEFORE, selfEnd, otherEnd);
+ final Instant 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);
}
}
@@ -758,11 +780,11 @@ abstract class TemporalOperation<T> implements
Serializable {
/** 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 = getBeginning(self)) != null) &&
- ((otherBegin = getBeginning(other)) != null) &&
compare(AFTER, selfBegin, otherBegin) &&
- ((otherEnd = getEnding (other)) != null) &&
compare(BEFORE, selfBegin, otherEnd) &&
- ((selfEnd = getEnding (self)) != null) &&
compare(AFTER, selfEnd, otherEnd);
+ final Instant 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);
}
}
@@ -789,11 +811,11 @@ abstract class TemporalOperation<T> implements
Serializable {
/** Condition defined by OGC filter specification. */
@Override public boolean evaluate(final Period self, final Period
other) {
- final Temporal selfBegin, selfEnd, otherBegin, otherEnd;
- return ((selfBegin = getBeginning(self)) != null) &&
- ((otherEnd = getEnding (other)) != null) &&
compare(BEFORE, selfBegin, otherEnd) &&
- ((selfEnd = getEnding (self)) != null) &&
- ((otherBegin = getBeginning(other)) != null) &&
compare(AFTER, selfEnd, otherBegin);
+ final Instant 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.metadata/main/org/apache/sis/temporal/DefaultInstant.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java
index 52064eeb7a..2ebefb315d 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java
@@ -18,7 +18,6 @@ package org.apache.sis.temporal;
import java.util.Objects;
import java.util.Optional;
-import java.io.Serializable;
import java.time.Duration;
import java.time.DateTimeException;
import java.time.ZonedDateTime;
@@ -26,6 +25,7 @@ import java.time.temporal.Temporal;
import java.time.temporal.TemporalAmount;
import org.apache.sis.util.Classes;
import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.resources.Errors;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -42,9 +42,13 @@ import org.opengis.temporal.IndeterminatePositionException;
* This is not the same as {@link java.time.Instant}, because the
* instant can actually be a date, or may be indeterminate.
*
+ * <h2>Thread-safety</h2>
+ * Instances of this class are mostly immutable, except for the list of
identifiers.
+ * All instances are thread-safe.
+ *
* @author Martin Desruisseaux (Geomatys)
*/
-final class DefaultInstant implements Instant, Serializable {
+final class DefaultInstant extends TemporalObject implements Instant {
/**
* For cross-version compatibility.
*/
@@ -226,6 +230,33 @@ cmp: if (canTestBefore | canTestAfter | canTestEqual) {
throw new
IndeterminatePositionException(Errors.format(Errors.Keys.IndeterminatePosition));
}
+ /**
+ * Compares this instant with the given object, optionally ignoring
timezone.
+ * If the comparison mode ignores metadata, this method compares only the
position on the timeline.
+ *
+ * @param other the object to compare to {@code this}.
+ * @param mode the strictness level of the comparison.
+ * @return {@code true} if both objects are equal according the given
comparison mode.
+ */
+ @Override
+ public boolean equals(final Object object, final ComparisonMode mode) {
+ if (mode.equals(ComparisonMode.STRICT)) { // Use `mode.equals(…)`
for opportunistic null check.
+ return equals(object);
+ }
+ if (object instanceof Instant) {
+ final var that = (Instant) object;
+ if (indeterminate == that.getIndeterminatePosition().orElse(null))
{
+ if (indeterminate == IndeterminateValue.NOW || indeterminate
== IndeterminateValue.UNKNOWN) {
+ return true;
+ }
+ final Temporal other = that.getPosition();
+ return Objects.equals(position, other) || // Needed in
all cases for testing null values.
+ (mode.isIgnoringMetadata() &&
TimeMethods.compareAny(TimeMethods.EQUAL, position, other));
+ }
+ }
+ return false;
+ }
+
/**
* Compares this instant with the given object for equality.
*/
@@ -236,7 +267,9 @@ cmp: if (canTestBefore | canTestAfter | canTestEqual) {
}
if (object instanceof DefaultInstant) {
final var that = (DefaultInstant) object;
- return Objects.equals(position, that.position) && indeterminate ==
that.indeterminate;
+ return indeterminate == that.indeterminate
+ && Objects.equals(position, that.position)
+ && equalIdentifiers(that);
}
return false;
}
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultPeriod.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultPeriod.java
index eb5dd82f5c..dff6b0edbd 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultPeriod.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultPeriod.java
@@ -16,8 +16,9 @@
*/
package org.apache.sis.temporal;
-import java.io.Serializable;
import java.time.temporal.TemporalAmount;
+import org.apache.sis.util.ComparisonMode;
+import org.apache.sis.util.Utilities;
// Specific to the geoapi-3.1 and geoapi-4.0 branches:
import org.opengis.temporal.Instant;
@@ -27,9 +28,13 @@ import org.opengis.temporal.Period;
/**
* Default implementation of GeoAPI period.
*
+ * <h2>Thread-safety</h2>
+ * Instances of this class are mostly immutable, except for the list of
identifiers.
+ * All instances are thread-safe.
+ *
* @author Martin Desruisseaux (Geomatys)
*/
-final class DefaultPeriod implements Period, Serializable {
+final class DefaultPeriod extends TemporalObject implements Period {
/**
* For cross-version compatibility.
*/
@@ -100,7 +105,30 @@ final class DefaultPeriod implements Period, Serializable {
}
if (obj instanceof DefaultPeriod) {
final var other = (DefaultPeriod) obj;
- return beginning.equals(other.beginning) &&
ending.equals(other.ending);
+ return beginning.equals(other.beginning)
+ && ending.equals(other.ending)
+ && equalIdentifiers(other);
+ }
+ return false;
+ }
+
+ /**
+ * Compares this period with the given object, optionally ignoring
timezone.
+ * If the comparison mode ignores metadata, this method compares only the
position on the timeline.
+ *
+ * @param other the object to compare to {@code this}.
+ * @param mode the strictness level of the comparison.
+ * @return {@code true} if both objects are equal according the given
comparison mode.
+ */
+ @Override
+ public boolean equals(final Object object, final ComparisonMode mode) {
+ if (mode.equals(ComparisonMode.STRICT)) { // Use `mode.equals(…)`
for opportunistic null check.
+ return equals(object);
+ }
+ if (object instanceof Period) {
+ final var other = (Period) object;
+ return Utilities.deepEquals(beginning, other.getBeginning(), mode)
&&
+ Utilities.deepEquals(ending, other.getEnding(), mode);
}
return false;
}
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/GeneralDuration.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/GeneralDuration.java
index 882721f7e5..6cd1724710 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/GeneralDuration.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/GeneralDuration.java
@@ -80,6 +80,20 @@ public final class GeneralDuration implements
TemporalAmount, Serializable {
this.time = time;
}
+ /**
+ * Returns the duration for the given components.
+ * If any component is zero, the other component is returned.
+ *
+ * @param period the period.
+ * @param time the component.
+ * @return the temporal amount from the given components.
+ */
+ public static TemporalAmount of(final Period period, final Duration time) {
+ if (period.isZero()) return time;
+ if (time.isZero()) return period;
+ return new GeneralDuration(period, time);
+ }
+
/**
* Parses a temporal amount which may contain a period and a duration part.
* This method returns a {@link Period} or {@link Duration} if those
objects
@@ -103,11 +117,8 @@ public final class GeneralDuration implements
TemporalAmount, Serializable {
if (previousLetter == 'P') {
return Duration.parse(text);
}
- var period = Period.parse(text.subSequence(0, i));
- var duration = Duration.parse(new StringBuilder(length - i
+ 1).append('P').append(text, i, length));
- if (duration.isZero()) return period;
- if (period.isZero()) return duration;
- return new GeneralDuration(period, duration);
+ return of(Period.parse(text.subSequence(0, i)),
+ Duration.parse(new StringBuilder(length - i +
1).append('P').append(text, i, length)));
}
previousLetter = c;
}
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalObject.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalObject.java
new file mode 100644
index 0000000000..d0b2801d21
--- /dev/null
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalObject.java
@@ -0,0 +1,89 @@
+/*
+ * 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.temporal;
+
+import java.util.List;
+import java.util.Collection;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.io.Serializable;
+import org.opengis.metadata.Identifier;
+import org.apache.sis.util.LenientComparable;
+import org.apache.sis.xml.IdentifiedObject;
+import org.apache.sis.xml.IdentifierMap;
+import org.apache.sis.xml.bind.ModifiableIdentifierMap;
+
+
+/**
+ * Base class of temporal objects. This class allows to associate identifiers
to this temporal object.
+ * The list of identifiers is modifiable because identifiers often need to be
added after creation time,
+ * for example in order to associate {@code gml:id} during XML unmarshalling.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ */
+abstract class TemporalObject implements IdentifiedObject, LenientComparable,
Serializable {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = 5408766446198380089L;
+
+ /**
+ * The identifier for this temporal object, or {@code null} if not yet
created.
+ */
+ private CopyOnWriteArrayList<Identifier> identifiers;
+
+ /**
+ * Creates a new temporal object with no identifier.
+ */
+ TemporalObject() {
+ }
+
+ /**
+ * Returns all identifiers associated to this temporal object.
+ *
+ * @return all identifiers associated to this object, or an empty
collection if none.
+ */
+ @Override
+ @SuppressWarnings("ReturnOfCollectionOrArrayField")
+ public final synchronized Collection<Identifier> getIdentifiers() {
+ if (identifiers == null) {
+ identifiers = new CopyOnWriteArrayList<>();
+ }
+ return identifiers;
+ }
+
+ /**
+ * Returns map view of the identifiers collection as
(<var>authority</var>, <var>code</var>) entries.
+ *
+ * @return the identifiers as a map of (<var>authority</var>,
<var>code</var>) entries, or an empty map if none.
+ */
+ @Override
+ public final IdentifierMap getIdentifierMap() {
+ return new ModifiableIdentifierMap(getIdentifiers());
+ }
+
+ /**
+ * Compares that identifiers of this temporal object with the identifiers
of the given object.
+ */
+ final boolean equalIdentifiers(final TemporalObject that) {
+ List<Identifier> id1, id2;
+ synchronized (this) {id1 = this.identifiers;}
+ synchronized (that) {id2 = that.identifiers;}
+ if (id1 == null) id1 = List.of();
+ if (id2 == null) id2 = List.of();
+ return id1.equals(id2);
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TimeMethods.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TimeMethods.java
index a949c3d6cf..f6b71c4910 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TimeMethods.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TimeMethods.java
@@ -40,6 +40,7 @@ import java.time.temporal.TemporalAccessor;
import java.lang.reflect.Modifier;
import java.io.Serializable;
import java.io.ObjectStreamException;
+import org.apache.sis.util.Classes;
import org.apache.sis.util.privy.Strings;
import org.apache.sis.util.resources.Errors;
@@ -159,6 +160,23 @@ public class TimeMethods<T> implements Serializable {
return compareAsInstants(test, accessor(self), other);
}
+ /**
+ * Returns {@code true} if both arguments are non-null and the specified
comparison evaluates to {@code true}.
+ * The type of the objects being compared is determined dynamically, which
has a performance cost.
+ * The {@code compare(…)} methods should be preferred when the type is
known in advance.
+ *
+ * @param test {@link #BEFORE}, {@link #AFTER} or {@link #EQUAL}.
+ * @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 DateTimeException if the two objects cannot be compared.
+ */
+ @SuppressWarnings("unchecked")
+ public static boolean compareAny(final int test, final Temporal self,
final Temporal other) {
+ return (self != null) && (other != null)
+ && compare(test, (Class)
Classes.findCommonClass(self.getClass(), other.getClass()), 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
@@ -351,7 +369,7 @@ public class TimeMethods<T> implements Serializable {
* @return the current time.
* @throws ClassCastException if the {@linkplain #type} is {@link Date} or
{@link MonthDay}.
*/
- final Temporal now() {
+ public final Temporal now() {
return (now != null) ? (Temporal) now.get() : ZonedDateTime.now();
}
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/IdentifierMapAdapter.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/IdentifierMapAdapter.java
index 902ff0076e..cfd40d5ae3 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/IdentifierMapAdapter.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/IdentifierMapAdapter.java
@@ -460,6 +460,7 @@ public class IdentifierMapAdapter extends
AbstractMap<Citation,String> implement
while (it.hasNext()) {
final Identifier identifier = it.next();
if (identifier != null) {
+ @SuppressWarnings("LocalVariableHidesMemberVariable")
final Citation authority = identifier.getAuthority();
final Boolean state = put(authority, Boolean.FALSE);
if (state == null) {
diff --git
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/extent/DefaultExtentTest.java
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/extent/DefaultExtentTest.java
index 63e08b0866..0c86b8dfe3 100644
---
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/extent/DefaultExtentTest.java
+++
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/extent/DefaultExtentTest.java
@@ -32,6 +32,7 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.apache.sis.metadata.xml.TestUsingFile;
import static org.apache.sis.metadata.Assertions.assertXmlEquals;
+import static org.apache.sis.test.Assertions.assertEqualsIgnoreMetadata;
/**
@@ -134,7 +135,7 @@ public final class DefaultExtentTest extends TestUsingFile {
assertMarshalEqualsFile(openTestFile(format), extent,
format.schemaVersion, STRICT,
new String[] {"gml:description"},
// Ignored nodes.
new String[] {"xmlns:*", "xsi:schemaLocation", "gml:id"});
// Ignored attributes.
- assertEquals(extent, unmarshalFile(DefaultExtent.class,
openTestFile(format)));
+ assertEqualsIgnoreMetadata(extent, unmarshalFile(DefaultExtent.class,
openTestFile(format)));
}
/**