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

Reply via email to