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
commit 0edda0c16163ddb013ab8bdc6043b6468cd6161b Author: Martin Desruisseaux <[email protected]> AuthorDate: Sun Jun 16 11:45:47 2024 +0200 Implements `DefaultPeriod.findRelativePosition()`. --- .../org/apache/sis/temporal/DefaultInstant.java | 65 +++++++++++++++----- .../org/apache/sis/temporal/DefaultPeriod.java | 71 ++++++++++++++++++++++ .../org/apache/sis/temporal/TemporalObjects.java | 33 +++++----- .../org/apache/sis/temporal/DefaultPeriodTest.java | 39 ++++++++++++ .../org/apache/sis/util/privy/LazyCandidate.java | 40 ++++++++++++ geoapi/snapshot | 2 +- 6 files changed, 216 insertions(+), 34 deletions(-) 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 2ebefb315d..0eb5682cbe 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 @@ -82,6 +82,28 @@ final class DefaultInstant extends TemporalObject implements Instant { this.indeterminate = indeterminate; } + /** + * Returns the given instant as an instance of this implementation class. + * + * @param other the other instance to cast or copy, or {@code null}. + * @return the given instant as an {@code DefaultInstant}, or {@code null} if the given value was null. + */ + public static DefaultInstant castOrCopy(final Instant other) { + if (other == null || other instanceof DefaultInstant) { + return (DefaultInstant) other; + } else { + final var indeterminate = other.getIndeterminatePosition().orElse(null); + if (indeterminate == IndeterminateValue.UNKNOWN) { + return UNKNOWN; + } + final Temporal position = other.getPosition(); + if (indeterminate == null || indeterminate == IndeterminateValue.BEFORE || indeterminate == IndeterminateValue.AFTER) { + Objects.requireNonNull(position); + } + return new DefaultInstant(position, indeterminate); + } + } + /** * Returns the date, time or position on the time-scale represented by this primitive. * Should not be null, unless the value is {@linkplain IndeterminateValue#UNKNOWN unknown}. @@ -140,7 +162,7 @@ final class DefaultInstant extends TemporalObject implements Instant { * when the two operands are {@code this} and {@code other}. * * @param other the other primitive for which to determine the relative position. - * @return a temporal operator which is true when evaluated between this primitive and the other primitive. + * @return a temporal operator which is true when evaluated between this instant and the other primitive. * @throws DateTimeException if the temporal objects cannot be compared. */ @Override @@ -150,25 +172,36 @@ final class DefaultInstant extends TemporalObject implements Instant { return relativeToInstant((Instant) other); } if (other instanceof Period) { - final var period = (Period) other; - TemporalOperatorName relation = relativeToInstant(period.getBeginning()); - String erroneous; - if (relation == TemporalOperatorName.BEFORE) return relation; - if (relation == TemporalOperatorName.EQUALS) return TemporalOperatorName.BEGINS; - if (relation == TemporalOperatorName.AFTER) { - relation = relativeToInstant(period.getEnding()); - if (relation == TemporalOperatorName.AFTER) return relation; - if (relation == TemporalOperatorName.EQUALS) return TemporalOperatorName.ENDS; - if (relation == TemporalOperatorName.BEFORE) return TemporalOperatorName.DURING; - erroneous = "ending"; - } else { - erroneous = "beginning"; - } - throw new DateTimeException(Errors.format(Errors.Keys.IllegalMapping_2, erroneous, relation)); + return relativeToPeriod((Period) other); } throw new DateTimeException(Errors.format(Errors.Keys.UnsupportedType_1, other.getClass())); } + /** + * Determines the position of this instant relative to a period. + * + * @param other the period for which to determine the relative position. + * @return a temporal operator which is true when evaluated between this primitive and the period. + * @throws DateTimeException if the temporal objects cannot be compared. + */ + final TemporalOperatorName relativeToPeriod(final Period other) { + TemporalOperatorName relation = relativeToInstant(other.getBeginning()); + String erroneous; + if (relation == TemporalOperatorName.BEFORE) return relation; + if (relation == TemporalOperatorName.EQUALS) return TemporalOperatorName.BEGINS; + if (relation == TemporalOperatorName.AFTER) { + relation = relativeToInstant(other.getEnding()); + if (relation == TemporalOperatorName.AFTER) return relation; + if (relation == TemporalOperatorName.EQUALS) return TemporalOperatorName.ENDS; + if (relation == TemporalOperatorName.BEFORE) return TemporalOperatorName.DURING; + erroneous = "ending"; + } else { + erroneous = "beginning"; + } + // Should never happen. + throw new DateTimeException(Errors.format(Errors.Keys.IllegalMapping_2, erroneous, relation)); + } + /** * Determines the position of this instant relative to another instant. * 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 dff6b0edbd..a5142cc6c0 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,13 +16,20 @@ */ package org.apache.sis.temporal; +import java.util.Map; +import java.time.DateTimeException; import java.time.temporal.TemporalAmount; +import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.ComparisonMode; import org.apache.sis.util.Utilities; +import org.apache.sis.util.resources.Errors; +import org.apache.sis.util.privy.LazyCandidate; // Specific to the geoapi-3.1 and geoapi-4.0 branches: import org.opengis.temporal.Instant; import org.opengis.temporal.Period; +import org.opengis.temporal.TemporalPrimitive; +import org.opengis.filter.TemporalOperatorName; /** @@ -78,6 +85,70 @@ final class DefaultPeriod extends TemporalObject implements Period { return GeneralDuration.distance(beginning, ending, false, false); } + /** + * Determines the position of this period relative to another temporal primitive. + * The relative position is identified by an operator which evaluates to {@code true} + * when the two operands are {@code this} and {@code other}. + * + * @param other the other primitive for which to determine the relative position. + * @return a temporal operator which is true when evaluated between this period and the other primitive. + * @throws DateTimeException if the temporal objects cannot be compared. + */ + @Override + public TemporalOperatorName findRelativePosition(final TemporalPrimitive other) { + ArgumentChecks.ensureNonNull("other", other); + if (other instanceof Instant) { + return DefaultInstant.castOrCopy((Instant) other).relativeToPeriod(this).reversed().orElseThrow(); + } + if (other instanceof Period) { + final var period = (Period) other; + String erroneous; + TemporalOperatorName relation = DefaultInstant.castOrCopy(beginning).relativeToPeriod(period); + final var map = POSITIONS.get(relation); + if (map != null) { + relation = DefaultInstant.castOrCopy(ending).relativeToPeriod(period); + final var result = map.get(relation); + if (result != null) { + return result; + } + erroneous = "ending"; + } else { + erroneous = "beginning"; + } + // Should never happen. + throw new DateTimeException(Errors.format(Errors.Keys.IllegalMapping_2, erroneous, relation)); + } + throw new DateTimeException(Errors.format(Errors.Keys.UnsupportedType_1, other.getClass())); + } + + /** + * Relative positions for given pairs (beginning, ending) relative positions. + * Keys of this static map are the relative positions of the beginning of this period relative to the other period. + * Keys of the enclosed maps are the relative positions of the ending of this period relative to the other period. + */ + @LazyCandidate + private static final Map<TemporalOperatorName, Map<TemporalOperatorName, TemporalOperatorName>> POSITIONS = Map.of( + TemporalOperatorName.BEFORE, Map.of( + TemporalOperatorName.BEFORE, TemporalOperatorName.BEFORE, + TemporalOperatorName.BEGINS, TemporalOperatorName.MEETS, + TemporalOperatorName.DURING, TemporalOperatorName.OVERLAPS, + TemporalOperatorName.ENDS, TemporalOperatorName.ENDED_BY, + TemporalOperatorName.AFTER, TemporalOperatorName.CONTAINS), + TemporalOperatorName.BEGINS, Map.of( + TemporalOperatorName.BEGINS, TemporalOperatorName.MEETS, + TemporalOperatorName.DURING, TemporalOperatorName.BEGINS, + TemporalOperatorName.ENDS, TemporalOperatorName.EQUALS, + TemporalOperatorName.AFTER, TemporalOperatorName.BEGUN_BY), + TemporalOperatorName.DURING, Map.of( + TemporalOperatorName.DURING, TemporalOperatorName.DURING, + TemporalOperatorName.ENDS, TemporalOperatorName.ENDS, + TemporalOperatorName.AFTER, TemporalOperatorName.OVERLAPPED_BY), + TemporalOperatorName.ENDS, Map.of( + TemporalOperatorName.ENDS, TemporalOperatorName.MET_BY, + TemporalOperatorName.AFTER, TemporalOperatorName.MET_BY), + TemporalOperatorName.AFTER, Map.of( + TemporalOperatorName.AFTER, TemporalOperatorName.AFTER)); + /** * Returns a string representation in ISO 8601 format. * The format is {@code <start>/<end>}. diff --git a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalObjects.java b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalObjects.java index 9063280bb3..027c086aa2 100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalObjects.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalObjects.java @@ -44,46 +44,45 @@ public final class TemporalObjects { /** * Creates an instant for the given Java temporal instant. * - * @param time the date for which to create instant, or {@code null}. + * @param position the date for which to create instant, or {@code null}. * @return the instant, or an unknown instant if the given time was null. */ - public static Instant createInstant(final Temporal time) { - return (time == null) ? DefaultInstant.UNKNOWN : new DefaultInstant(time, null); + public static Instant createInstant(final Temporal position) { + return (position == null) ? DefaultInstant.UNKNOWN : new DefaultInstant(position, null); } /** * Creates an instant for the given Java temporal instant associated to the indeterminate value. * This is used for creating "before" or "after" instant. * - * @param time the date for which to create instant. - * @param value the indeterminate value. + * @param position the date and/or time for which to create instant. + * @param indeterminate the indeterminate value, or {@code null} if the value is not indeterminate. * @return the instant. */ - public static Instant createInstant(final Temporal time, final IndeterminateValue value) { - ArgumentChecks.ensureNonNull("value", value); - if (value == IndeterminateValue.UNKNOWN) { + public static Instant createInstant(final Temporal position, final IndeterminateValue indeterminate) { + if (indeterminate == IndeterminateValue.UNKNOWN) { return DefaultInstant.UNKNOWN; } - if (value == IndeterminateValue.BEFORE || value == IndeterminateValue.AFTER) { - ArgumentChecks.ensureNonNull("time", time); + if (indeterminate == null || indeterminate == IndeterminateValue.BEFORE || indeterminate == IndeterminateValue.AFTER) { + ArgumentChecks.ensureNonNull("position", position); } - return new DefaultInstant(time, value); + return new DefaultInstant(position, indeterminate); } /** * Creates an instant for the given indeterminate value. * The given value cannot be "before" or "after". * - * @param value the indeterminate value. + * @param indeterminate the indeterminate value. * @return the instant for the given indeterminate value. * @throws IllegalArgumentException if the given value is "before" or "after". */ - public static Instant createInstant(final IndeterminateValue value) { - ArgumentChecks.ensureNonNull("value", value); - if (value == IndeterminateValue.BEFORE || value == IndeterminateValue.AFTER) { - throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "value", value)); + public static Instant createInstant(final IndeterminateValue indeterminate) { + ArgumentChecks.ensureNonNull("indeterminate", indeterminate); + if (indeterminate == IndeterminateValue.BEFORE || indeterminate == IndeterminateValue.AFTER) { + throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "indeterminate", indeterminate)); } - return (value == IndeterminateValue.UNKNOWN) ? DefaultInstant.UNKNOWN : new DefaultInstant(null, value); + return (indeterminate == IndeterminateValue.UNKNOWN) ? DefaultInstant.UNKNOWN : new DefaultInstant(null, indeterminate); } /** diff --git a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultPeriodTest.java b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultPeriodTest.java index f913813e03..9fbeb6c8f9 100644 --- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultPeriodTest.java +++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultPeriodTest.java @@ -18,11 +18,14 @@ package org.apache.sis.temporal; import java.time.LocalDate; import java.time.Period; +import java.time.Year; +import org.opengis.temporal.TemporalPrimitive; // Test dependencies import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; import org.apache.sis.test.TestCase; +import org.opengis.filter.TemporalOperatorName; /** @@ -86,4 +89,40 @@ public final class DefaultPeriodTest extends TestCase { var period = TemporalObjects.createPeriod(beginning, ending); assertEquals(Period.of(5, 3, 5), period.length()); } + + /** + * Tests {@link DefaultPeriod#findRelativePosition(TemporalPrimitive)}. + */ + @Test + public void testFindRelativePosition() { + var p04 = TemporalObjects.createPeriod(Year.of(2000), Year.of(2004)); + var p56 = TemporalObjects.createPeriod(Year.of(2005), Year.of(2006)); + var p13 = TemporalObjects.createPeriod(Year.of(2001), Year.of(2003)); + var p14 = TemporalObjects.createPeriod(Year.of(2001), Year.of(2004)); + assertRelativePositionEquals(TemporalOperatorName.EQUALS, p04, p04); + assertRelativePositionEquals(TemporalOperatorName.BEFORE, p04, p56); + assertRelativePositionEquals(TemporalOperatorName.CONTAINS, p04, p13); + assertRelativePositionEquals(TemporalOperatorName.ENDED_BY, p04, p14); + assertRelativePositionEquals(TemporalOperatorName.EQUALS, p56, p56); + assertRelativePositionEquals(TemporalOperatorName.AFTER, p56, p13); + assertRelativePositionEquals(TemporalOperatorName.AFTER, p56, p14); + assertRelativePositionEquals(TemporalOperatorName.EQUALS, p13, p13); + assertRelativePositionEquals(TemporalOperatorName.BEGINS, p13, p14); + assertRelativePositionEquals(TemporalOperatorName.EQUALS, p14, p14); + } + + /** + * Finds the relative position of {@code p1} relative to {@code p2} and compare against the expected value. + * Then reverses argument order and test again. + * + * @param expected the expected result. + * @param self period for which to find the relative position. + * @param other the period against which {@code self} is compared. + */ + private static void assertRelativePositionEquals(TemporalOperatorName expected, + org.opengis.temporal.Period self, org.opengis.temporal.Period other) + { + assertEquals(expected, self.findRelativePosition(other)); + assertEquals(expected.reversed().orElseThrow(), other.findRelativePosition(self)); + } } diff --git a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/LazyCandidate.java b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/LazyCandidate.java new file mode 100644 index 0000000000..46acf276b7 --- /dev/null +++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/LazyCandidate.java @@ -0,0 +1,40 @@ +/* + * 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.util.privy; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Annotates fields that may be replaced by lazy initialization. + * This is a marker annotation for code to revisit if and when the + * JEP for Lazy Static Final Fields become available. + * + * <h2>Alternative</h2> + * We could have used the inner class pattern instead, but it it not clear that it is worth the cost. + * + * @author Martin Desruisseaux (Geomatys) + * + * @see <a href="https://openjdk.org/jeps/8209964">JEP draft: Lazy Static Final Fields</a> + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.SOURCE) +public @interface LazyCandidate { +} diff --git a/geoapi/snapshot b/geoapi/snapshot index 044faffa03..2c16f9a1d5 160000 --- a/geoapi/snapshot +++ b/geoapi/snapshot @@ -1 +1 @@ -Subproject commit 044faffa03ac7337c5c3d5af3fe73fb9a30cce05 +Subproject commit 2c16f9a1d5c2bd2b7c4b1666f877232f162bbc30
