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 <martin.desruisse...@geomatys.com> 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))); } /**