This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/sis.git
commit 86b20ccbdab615b3b94bc9a32bd08d323f4a0c06 Merge: bb9d125402 f358804252 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Mon Jun 24 00:01:23 2024 +0200 Merge branch 'geoapi-3.1' .../org/apache/sis/coverage/grid/PixelInCell.java | 3 + .../main/org/apache/sis/filter/TemporalFilter.java | 22 +- .../org/apache/sis/filter/TemporalOperation.java | 107 ++++-- .../main/org/apache/sis/filter/privy/Visitor.java | 2 +- .../apache/sis/geometry/wrapper/Geometries.java | 1 + .../apache/sis/geometry/wrapper/esri/Factory.java | 5 +- .../test/org/apache/sis/filter/PeriodLiteral.java | 34 +- .../org/apache/sis/filter/TemporalFilterTest.java | 2 +- .../org.apache.sis.metadata/main/module-info.java | 12 + .../apache/sis/metadata/iso/DefaultMetadata.java | 2 +- .../sis/metadata/iso/acquisition/DefaultEvent.java | 2 +- .../iso/acquisition/DefaultRequestedDate.java | 2 +- .../iso/acquisition/DefaultRequirement.java | 2 +- .../sis/metadata/iso/citation/DefaultCitation.java | 2 +- .../metadata/iso/citation/DefaultCitationDate.java | 4 +- .../distribution/DefaultStandardOrderProcess.java | 2 +- .../metadata/iso/extent/DefaultTemporalExtent.java | 12 +- .../apache/sis/metadata/iso/extent/Extents.java | 21 +- .../metadata/iso/identification/DefaultUsage.java | 21 +- .../sis/metadata/iso/legacy/TemporalToDate.java | 2 +- .../metadata/iso/lineage/DefaultProcessStep.java | 25 +- .../org/apache/sis/metadata/sql/privy/Dialect.java | 22 +- .../geoapi/temporal/IndeterminateValue.java} | 17 +- .../sis/pending/geoapi/temporal/Instant.java} | 31 +- .../apache/sis/pending/geoapi/temporal/Period.java | 17 +- .../geoapi/temporal}/TemporalOperatorName.java | 28 +- .../sis/pending/geoapi/temporal/package-info.java | 0 .../apache/sis/pending/temporal/DefaultPeriod.java | 71 ---- .../sis/pending/temporal/TemporalUtilities.java | 137 -------- .../org/apache/sis/temporal/DefaultInstant.java | 335 +++++++++++++++++++ .../org/apache/sis/temporal/DefaultPeriod.java | 206 ++++++++++++ .../temporal/DefaultPeriodDuration.java | 9 +- .../org/apache/sis/temporal/GeneralDuration.java | 369 +++++++++++++++++++++ .../apache/sis/temporal/LenientDateFormat.java} | 17 +- .../org/apache/sis/temporal}/TemporalDate.java | 38 +-- .../org/apache/sis/temporal/TemporalObject.java | 89 +++++ .../org/apache/sis/temporal/TemporalObjects.java | 154 +++++++++ .../main/org/apache/sis/temporal}/TimeMethods.java | 120 ++++--- .../sis/{pending => }/temporal/package-info.java | 9 +- .../apache/sis/xml/bind/IdentifierMapAdapter.java | 1 + .../org/apache/sis/xml/bind/gml/TM_Primitive.java | 14 +- .../apache/sis/xml/bind/gml/TimePeriodBound.java | 19 +- .../apache/sis/xml/bind/gts/TM_PeriodDuration.java | 2 +- .../org/apache/sis/xml/privy/XmlUtilities.java | 2 +- .../iso/citation/DefaultCitationDateTest.java | 10 +- .../sis/metadata/iso/extent/DefaultExtentTest.java | 3 +- .../apache/sis/temporal/DefaultInstantTest.java | 215 ++++++++++++ .../org/apache/sis/temporal/DefaultPeriodTest.java | 131 ++++++++ .../GeneralDurationTest.java} | 35 +- .../sis/temporal/LenientDateFormatTest.java} | 46 +-- .../apache/sis/xml/bind/gml/TimePeriodTest.java | 17 +- .../org/apache/sis/geometry/CoordinateFormat.java | 2 +- .../main/org/apache/sis/io/wkt/AbstractParser.java | 4 +- .../main/org/apache/sis/io/wkt/Formatter.java | 6 +- .../apache/sis/io/wkt/GeodeticObjectParser.java | 2 +- .../main/org/apache/sis/io/wkt/WKTFormat.java | 6 +- .../sis/referencing/crs/DefaultTemporalCRS.java | 2 +- .../sis/referencing/datum/AbstractDatum.java | 2 +- .../referencing/datum/DefaultTemporalDatum.java | 2 +- .../referencing/factory/sql/EPSGDataAccess.java | 6 +- .../operation/CoordinateOperationFinder.java | 2 +- .../sis/referencing/privy/AxisDirections.java | 11 + .../sis/referencing/privy/ExtentSelector.java | 2 +- .../referencing/privy/GeodeticObjectBuilder.java | 2 +- .../test/org/apache/sis/io/wkt/ElementTest.java | 2 +- .../sis/io/wkt/GeodeticObjectParserTest.java | 2 +- .../org/apache/sis/referencing/CommonCRSTest.java | 2 +- .../datum/DefaultTemporalDatumTest.java | 2 +- .../sis/referencing/privy/AxisDirectionsTest.java | 3 +- .../sis/test/integration/MetadataVerticalTest.java | 2 +- .../sis/storage/geotiff/reader/XMLMetadata.java | 6 +- .../storage/geotiff/reader/XMLMetadataTest.java | 4 +- .../apache/sis/storage/netcdf/base/CRSBuilder.java | 2 +- .../sis/storage/netcdf/classic/ChannelDecoder.java | 8 +- .../sis/storage/netcdf/classic/VariableInfo.java | 4 +- .../apache/sis/storage/netcdf/base/TestCase.java | 2 +- .../sis/storage/sql/feature/FeatureStream.java | 4 +- .../sis/storage/sql/feature/GeometryGetter.java | 48 ++- .../main/org/apache/sis/storage/gpx/Copyright.java | 2 +- .../sis/storage/xml/stream/StaxStreamReader.java | 6 +- .../main/org/apache/sis/storage/csv/Store.java | 6 +- .../org/apache/sis/storage/csv/TimeEncoding.java | 6 +- .../src/org.apache.sis.util/main/module-info.java | 4 - .../main/org/apache/sis/measure/RangeFormat.java | 44 ++- .../main/org/apache/sis/pending/jdk/JDK18.java | 11 + .../privy/LazyCandidate.java} | 26 +- .../main/org/apache/sis/util/resources/Errors.java | 7 +- .../apache/sis/util/resources/Errors.properties | 3 +- .../apache/sis/util/resources/Errors_fr.properties | 1 + .../sis/storage/shapefile/ShapefileStore.java | 8 +- .../sis/storage/shapefile/shp/ShapeWriter.java | 4 +- .../sis/storage/shapefile/shx/IndexWriter.java | 6 +- 92 files changed, 2162 insertions(+), 561 deletions(-) diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/PixelInCell.java index cd33d042c1,5b2b7a1f8d..a50a4ad129 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/PixelInCell.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/PixelInCell.java @@@ -109,7 -113,22 +109,10 @@@ public enum PixelInCell * * @return all names of this constant. This array is never null and never empty. */ - @Override public String[] names() { + if (this == CELL_CENTER) { + return new String[] {name(), identifier, "cell centre"}; + } return new String[] {name(), identifier}; } - - /** - * Returns the enumeration of the same kind as this item. - * This is equivalent to {@link #values()}. - * - * @return the enumeration of the same kind as this item. - */ - @Override - public ControlledVocabulary[] family() { - return values(); - } } diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java index 21629a0fda,38d1984a85..4d3b5d4bea --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalFilter.java @@@ -16,12 -16,19 +16,15 @@@ */ package org.apache.sis.filter; + import java.time.DateTimeException; import org.apache.sis.util.Classes; + import org.apache.sis.util.resources.Errors; + import org.apache.sis.temporal.TimeMethods; import org.apache.sis.feature.privy.FeatureExpression; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.temporal.Period; -import org.opengis.filter.Filter; -import org.opengis.filter.Expression; -import org.opengis.filter.TemporalOperator; -import org.opengis.filter.TemporalOperatorName; -import org.opengis.filter.InvalidFilterValueException; +// Specific to the main branch: +import org.apache.sis.pending.geoapi.temporal.Period; - import org.apache.sis.pending.geoapi.filter.TemporalOperatorName; ++import org.apache.sis.pending.geoapi.temporal.TemporalOperatorName; /** @@@ -172,6 -179,8 +175,8 @@@ class TemporalFilter<R,T> extends Binar /** * Determines if the test(s) represented by this filter passes with the given operands. * Values of {@link #expression1} and {@link #expression2} shall be two single values. + * - * @throws InvalidFilterValueException if two temporal objects cannot be compared. ++ * @throws IllegalArgumentException if two temporal objects cannot be compared. */ @Override public boolean test(final R candidate) { @@@ -190,6 -199,9 +195,9 @@@ } else { return operation.evaluate(left, right); } + } catch (DateTimeException e) { - throw new InvalidFilterValueException(Errors.format( ++ throw new IllegalArgumentException(Errors.format( + Errors.Keys.CannotCompareInstanceOf_2, left.getClass(), right.getClass())); } } return false; @@@ -224,8 -236,11 +232,11 @@@ final T left = expression1.apply(candidate); if (left != null) { final T right = expression2.apply(candidate); - if (right != null) { + if (right != null) try { return operation.evaluate(left, right); + } catch (DateTimeException e) { - throw new InvalidFilterValueException(Errors.format( ++ throw new IllegalArgumentException(Errors.format( + Errors.Keys.CannotCompareInstanceOf_2, left.getClass(), right.getClass())); } } return false; @@@ -261,8 -276,11 +272,11 @@@ final Period left = expression1.apply(candidate); if (left != null) { final Period right = expression2.apply(candidate); - if (right != null) { + if (right != null) try { return operation.evaluate(left, right); + } catch (DateTimeException e) { - throw new InvalidFilterValueException(Errors.format( ++ throw new IllegalArgumentException(Errors.format( + Errors.Keys.CannotCompareInstanceOf_2, left.getClass(), right.getClass())); } } return false; diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/TemporalOperation.java index db72aafd22,a59b174dc6..c95e471866 --- 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,18 -16,22 +16,22 @@@ */ 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 static org.apache.sis.filter.TimeMethods.BEFORE; - import static org.apache.sis.filter.TimeMethods.AFTER; - import static org.apache.sis.filter.TimeMethods.EQUAL; + import org.apache.sis.temporal.TimeMethods; + import static org.apache.sis.temporal.TimeMethods.BEFORE; + import static org.apache.sis.temporal.TimeMethods.AFTER; + 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; +// Specific to the main branch: +import org.apache.sis.pending.geoapi.temporal.Period; - import org.apache.sis.pending.geoapi.filter.TemporalOperatorName; ++import org.apache.sis.pending.geoapi.temporal.Instant; ++import org.apache.sis.pending.geoapi.temporal.IndeterminateValue; ++import org.apache.sis.pending.geoapi.temporal.TemporalOperatorName; /** diff --cc endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/Visitor.java index f6b47d6f17,b314efe3de..517433d4bb --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/Visitor.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/privy/Visitor.java @@@ -23,14 -23,15 +23,14 @@@ import java.util.Collections import java.util.function.BiConsumer; import org.apache.sis.feature.internal.Resources; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.util.CodeList; -import org.opengis.filter.Filter; -import org.opengis.filter.Expression; -import org.opengis.filter.LogicalOperatorName; -import org.opengis.filter.SpatialOperatorName; -import org.opengis.filter.DistanceOperatorName; -import org.opengis.filter.TemporalOperatorName; -import org.opengis.filter.ComparisonOperatorName; +// Specific to the main branch: +import org.apache.sis.filter.Filter; +import org.apache.sis.filter.Expression; +import org.apache.sis.pending.geoapi.filter.LogicalOperatorName; +import org.apache.sis.pending.geoapi.filter.SpatialOperatorName; +import org.apache.sis.pending.geoapi.filter.DistanceOperatorName; - import org.apache.sis.pending.geoapi.filter.TemporalOperatorName; +import org.apache.sis.pending.geoapi.filter.ComparisonOperatorName; ++import org.apache.sis.pending.geoapi.temporal.TemporalOperatorName; /** diff --cc endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/PeriodLiteral.java index faa3eb8cf3,f547361c23..e724f0968b --- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/PeriodLiteral.java +++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/PeriodLiteral.java @@@ -18,11 -18,13 +18,12 @@@ package org.apache.sis.filter import java.time.Instant; import java.io.Serializable; + import org.apache.sis.temporal.TemporalObjects; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.feature.Feature; -import org.opengis.filter.Expression; -import org.opengis.filter.Literal; -import org.opengis.temporal.Period; +// Specific to the main branch: +import org.apache.sis.feature.AbstractFeature; +import org.apache.sis.pending.geoapi.filter.Literal; +import org.apache.sis.pending.geoapi.temporal.Period; /** @@@ -47,17 -49,35 +48,40 @@@ final class PeriodLiteral implements Pe /** * Returns the constant value held by this object. */ - @Override public Period getValue() {return this;} + @Override + public Period getValue() { + return this; + } + + @Override public Period apply(AbstractFeature input) {return this;} + - /** Returns a bound of this period. */ - @Override public Instant getBeginning() {return Instant.ofEpochMilli(begin);} - @Override public Instant getEnding() {return Instant.ofEpochMilli(end);} + /** + * Returns the beginning of this period. + */ + @Override - public org.opengis.temporal.Instant getBeginning() { ++ public org.apache.sis.pending.geoapi.temporal.Instant getBeginning() { + return TemporalObjects.createInstant(Instant.ofEpochMilli(begin)); + } + + /** + * Returns the ending of this period. + */ + @Override - public org.opengis.temporal.Instant getEnding() { ++ public org.apache.sis.pending.geoapi.temporal.Instant getEnding() { + return TemporalObjects.createInstant(Instant.ofEpochMilli(end)); + } + + /** + * Not needed for the tests. + */ + @Override - public <N> Expression<Feature,N> toValueType(Class<N> target) { ++ public <N> Expression<AbstractFeature,N> toValueType(Class<N> target) { + throw new UnsupportedOperationException(); + } + /** Not needed for the tests. */ - @Override public <N> Expression<AbstractFeature,N> toValueType(Class<N> target) {throw new UnsupportedOperationException();} + @Override public Class<AbstractFeature> getResourceClass() {return AbstractFeature.class;} + /** * Hash code value. Used by the tests for checking the results of deserialization. */ diff --cc endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/TemporalFilterTest.java index a07fd7b129,b349e62672..7c043c6476 --- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/TemporalFilterTest.java +++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/TemporalFilterTest.java @@@ -26,10 -26,12 +26,10 @@@ import static org.junit.jupiter.api.Ass import org.apache.sis.test.TestCase; import static org.apache.sis.test.Assertions.assertSerializedEquals; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.temporal.Period; -import org.opengis.feature.Feature; -import org.opengis.filter.FilterFactory; -import org.opengis.filter.TemporalOperator; -import org.opengis.filter.TemporalOperatorName; +// Specific to the main branch: +import org.apache.sis.feature.AbstractFeature; +import org.apache.sis.pending.geoapi.temporal.Period; - import org.apache.sis.pending.geoapi.filter.TemporalOperatorName; ++import org.apache.sis.pending.geoapi.temporal.TemporalOperatorName; /** diff --cc endorsed/src/org.apache.sis.metadata/main/module-info.java index d4ac51c38b,2f35b34abf..eae5879e88 --- a/endorsed/src/org.apache.sis.metadata/main/module-info.java +++ b/endorsed/src/org.apache.sis.metadata/main/module-info.java @@@ -76,6 -76,15 +76,18 @@@ module org.apache.sis.metadata /* * Internal API open only to other Apache SIS modules. */ ++ exports org.apache.sis.pending.geoapi.temporal to ++ org.apache.sis.feature; ++ + exports org.apache.sis.temporal to + org.apache.sis.referencing, + org.apache.sis.feature, + org.apache.sis.storage, + org.apache.sis.storage.xml, + org.apache.sis.storage.netcdf, + org.apache.sis.storage.geotiff, + org.apache.sis.cql; // In the "incubator" sub-project. + exports org.apache.sis.metadata.privy to org.apache.sis.referencing, org.apache.sis.feature, diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultEvent.java index 1b72a5ab4d,732bc85b94..49942940fe --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultEvent.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/acquisition/DefaultEvent.java @@@ -31,13 -31,8 +31,13 @@@ import org.opengis.metadata.acquisition import org.opengis.metadata.acquisition.Sequence; import org.opengis.metadata.acquisition.Trigger; import org.apache.sis.metadata.iso.ISOMetadata; - import org.apache.sis.util.privy.TemporalDate; + import org.apache.sis.temporal.TemporalDate; +// Specific to the main branch: +import org.opengis.annotation.UML; +import static org.opengis.annotation.Obligation.MANDATORY; +import static org.opengis.annotation.Specification.ISO_19115_2; + /** * Identification of a significant collection point within an operation. diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultCitationDate.java index 107f978fea,8f75d6a434..3a9d3ddd7b --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultCitationDate.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/citation/DefaultCitationDate.java @@@ -25,13 -25,8 +25,13 @@@ import org.opengis.metadata.citation.Ci import org.opengis.metadata.citation.DateType; import org.apache.sis.metadata.TitleProperty; import org.apache.sis.metadata.iso.ISOMetadata; - import org.apache.sis.util.privy.TemporalDate; + import org.apache.sis.temporal.TemporalDate; +// Specific to the main branch: +import org.opengis.annotation.UML; +import static org.opengis.annotation.Obligation.MANDATORY; +import static org.opengis.annotation.Specification.ISO_19115; + /** * Reference date and event used to describe it. diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java index d2f256f7de,207f5ce244..074078276d --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/distribution/DefaultStandardOrderProcess.java @@@ -30,13 -30,8 +30,13 @@@ import org.opengis.metadata.distributio import org.apache.sis.xml.bind.gco.GO_RecordType; import org.apache.sis.xml.bind.gco.GO_Record; import org.apache.sis.metadata.iso.ISOMetadata; - import org.apache.sis.util.privy.TemporalDate; + import org.apache.sis.temporal.TemporalDate; +// Specific to the main branch: +import org.opengis.annotation.UML; +import static org.opengis.annotation.Obligation.OPTIONAL; +import static org.opengis.annotation.Specification.ISO_19115; + /** * Common ways in which the resource may be obtained or received, and related instructions diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultUsage.java index 06b4b7afc0,a47f0e1dea..6c60fc875c --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultUsage.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/identification/DefaultUsage.java @@@ -41,12 -41,6 +41,11 @@@ import org.apache.sis.util.iso.Types // Specific to the main and geoapi-3.1 branches: import org.opengis.metadata.citation.ResponsibleParty; +// Specific to the main branch: +import org.opengis.annotation.UML; +import static org.opengis.annotation.Obligation.OPTIONAL; +import static org.opengis.annotation.Specification.ISO_19115; - import org.apache.sis.pending.temporal.DefaultPeriod; + /** * Brief description of ways in which the resource(s) is/are currently or has been used. @@@ -173,20 -167,12 +172,20 @@@ public class DefaultUsage extends ISOMe super(object); if (object != null) { specificUsage = object.getSpecificUsage(); - usageDates = copyCollection(object.getUsageDates(), TemporalPrimitive.class); userDeterminedLimitations = object.getUserDeterminedLimitations(); userContactInfo = copyCollection(object.getUserContactInfo(), ResponsibleParty.class); - responses = copyCollection(object.getResponses(), InternationalString.class); - additionalDocumentation = copyCollection(object.getAdditionalDocumentation(), Citation.class); - identifiedIssues = copyCollection(object.getIdentifiedIssues(), Citation.class); + if (object instanceof DefaultUsage) { + final var c = (DefaultUsage) object; + usageDates = copyCollection(c.getUsageDates(), TemporalPrimitive.class); + responses = copyCollection(c.getResponses(), InternationalString.class); + additionalDocumentation = copyCollection(c.getAdditionalDocumentation(), Citation.class); + identifiedIssues = copyCollection(c.getIdentifiedIssues(), Citation.class); + } else { - TemporalPrimitive t = TemporalUtilities.createInstant(TemporalDate.toTemporal(object.getUsageDate())); ++ Date t = object.getUsageDate(); + if (t != null) { - usageDates = List.of(t); ++ usageDates = List.of(TemporalObjects.createInstant(TemporalDate.toTemporal(t))); + } + } } } diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java index 9fa6dcf771,feb25054d3..e45767dde6 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/lineage/DefaultProcessStep.java @@@ -41,12 -41,10 +41,13 @@@ import org.apache.sis.temporal.Temporal // Specific to the main and geoapi-3.1 branches: import org.opengis.metadata.citation.ResponsibleParty; + import org.apache.sis.temporal.TemporalDate; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.metadata.maintenance.Scope; +// Specific to the main branch: +import org.opengis.metadata.quality.Scope; +import org.opengis.annotation.UML; +import static org.opengis.annotation.Obligation.OPTIONAL; +import static org.opengis.annotation.Specification.ISO_19115; /** @@@ -189,16 -187,14 +190,17 @@@ public class DefaultProcessStep extend if (object != null) { description = object.getDescription(); rationale = object.getRationale(); - stepDateTime = TemporalUtilities.createInstant(object.getDate()); + stepDateTime = toInstant(object.getDate()); processors = copyCollection(object.getProcessors(), ResponsibleParty.class); - references = copyCollection(object.getReferences(), Citation.class); sources = copyCollection(object.getSources(), Source.class); - scope = object.getScope(); outputs = copyCollection(object.getOutputs(), Source.class); processingInformation = object.getProcessingInformation(); reports = copyCollection(object.getReports(), ProcessStepReport.class); + if (object instanceof DefaultProcessStep) { - references = copyCollection(((DefaultProcessStep) object).getReferences(), Citation.class); - scope = ((DefaultProcessStep) object).getScope(); ++ final var c = (DefaultProcessStep) object; ++ references = copyCollection(c.getReferences(), Citation.class); ++ scope = c.getScope(); + } } } @@@ -305,7 -302,14 +307,11 @@@ @Deprecated(since="1.0") @XmlElement(name = "dateTime", namespace = LegacyNamespaces.GMD) public Date getDate() { - return FilterByVersion.LEGACY_METADATA.accept() ? TemporalUtilities.getAnyDate(getStepDateTime()) : null; + if (FilterByVersion.LEGACY_METADATA.accept()) { + Date date = TemporalObjects.getAnyDate(getStepDateTime()); - if (date == null) { - date = ProcessStep.super.getDate(); - } + return date; + } + return null; } /** diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/IndeterminateValue.java index a30135526f,0bddfc2b60..7377346529 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/IndeterminateValue.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/IndeterminateValue.java @@@ -14,19 -14,17 +14,20 @@@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.sis.pending.geoapi.filter; -package org.apache.sis.map.service.se1; ++package org.apache.sis.pending.geoapi.temporal; ++ ++import java.util.Locale; /** - * Placeholder for GeoAPI 3.1 interfaces (not yet released). - * Shall not be visible in public API, as it will be deleted after next GeoAPI release. - * Subclassed by symbolizer who have resource caches. - * This could contain images or models. - * The cache instance may be shared by multiple {@link SymbolizerToScene2D} instances - * using method {@link SymbolizerToScene2D#sharedCache(org.apache.sis.internal.renderer.SymbolizerCache) }. ++ * Indications that a temporal position is not precisely known. + * - * @author Johann Sorel (Geomatys) ++ * @author Martin Desruisseaux (Geomatys) */ - @SuppressWarnings("doclint:missing") - public enum TemporalOperatorName { - AFTER, BEFORE, BEGINS, BEGUN_BY, CONTAINS, DURING, EQUALS, OVERLAPS, MEETS, ENDS, - OVERLAPPED_BY, MET_BY, ENDED_BY, ANY_INTERACTS; -public abstract class SymbolizerCache { ++public enum IndeterminateValue { ++ UNKNOWN, NOW, BEFORE, AFTER; + public String identifier() { - return name().toLowerCase(); ++ return name().toLowerCase(Locale.US); + } } diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/Instant.java index f8ccb863ff,0000000000..02b3685f77 mode 100644,000000..100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/Instant.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/Instant.java @@@ -1,44 -1,0 +1,57 @@@ +/* + * 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.pending.geoapi.temporal; + ++import java.util.Optional; +import java.time.temporal.Temporal; ++import java.time.temporal.TemporalAmount; +import org.opengis.temporal.TemporalPrimitive; + + +/** + * Placeholder for a GeoAPI interfaces not present in GeoAPI 3.0. + * + * @author Martin Desruisseaux (Geomatys) - * @since 0.3 ++ * @since 1.5 + * @version 1.5 + */ - public interface Period extends TemporalPrimitive { ++public interface Instant extends TemporalPrimitive { + /** - * Links this period to the instant at which it ends. - * - * @return The beginning instant. ++ * Returns the date, time or position on the time-scale represented by this primitive. + */ - Temporal getBeginning(); ++ Temporal getPosition(); + + /** - * Links this period to the instant at which it ends. ++ * Returns the reason why the temporal position is missing or inaccurate. + * - * @return The end instant. ++ * @return the reason why the position is indeterminate. + */ - Temporal getEnding(); ++ default Optional<IndeterminateValue> getIndeterminatePosition() { ++ return Optional.empty(); ++ } ++ ++ /** ++ * Returns the distance from this instant to another instant or a period (optional operation). ++ */ ++ default TemporalAmount distance(TemporalPrimitive other) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ default TemporalOperatorName findRelativePosition(TemporalPrimitive other) { ++ throw new UnsupportedOperationException(); ++ } +} diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/Period.java index f8ccb863ff,a6eeffe0b8..1212795911 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/Period.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/Period.java @@@ -14,31 -14,32 +14,42 @@@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.sis.xml.bind.referencing; +package org.apache.sis.pending.geoapi.temporal; - import java.time.temporal.Temporal; -import org.opengis.referencing.datum.VerticalDatumType; -import org.apache.sis.xml.bind.gml.CodeListAdapter; ++import java.time.temporal.TemporalAmount; +import org.opengis.temporal.TemporalPrimitive; /** - * JAXB adapter for (un)marshalling of GeoAPI code list. + * Placeholder for a GeoAPI interfaces not present in GeoAPI 3.0. * * @author Martin Desruisseaux (Geomatys) + * @since 0.3 + * @version 1.5 */ -@SuppressWarnings("deprecation") -public final class CD_VerticalDatumType extends CodeListAdapter<VerticalDatumType> { +public interface Period extends TemporalPrimitive { /** - * Empty constructor for JAXB only. + * Links this period to the instant at which it ends. + * + * @return The beginning instant. */ - Temporal getBeginning(); - public CD_VerticalDatumType() { - } ++ Instant getBeginning(); /** - * {@inheritDoc} + * Links this period to the instant at which it ends. * - * @return {@code VerticalDatumType.class} + * @return The end instant. */ - Temporal getEnding(); - @Override - protected Class<VerticalDatumType> getCodeListClass() { - return VerticalDatumType.class; ++ Instant getEnding(); ++ ++ /** ++ * Returns the duration of this period (optional operation). ++ */ ++ default TemporalAmount length() { ++ return getBeginning().distance(getEnding()); ++ } ++ ++ default TemporalOperatorName findRelativePosition(TemporalPrimitive other) { ++ throw new UnsupportedOperationException(); + } } diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/TemporalOperatorName.java index a30135526f,0000000000..50a05aa191 mode 100644,000000..100644 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/TemporalOperatorName.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/pending/geoapi/temporal/TemporalOperatorName.java @@@ -1,32 -1,0 +1,54 @@@ +/* + * 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.pending.geoapi.filter; ++package org.apache.sis.pending.geoapi.temporal; ++ ++import java.util.Optional; + + +/** + * Placeholder for GeoAPI 3.1 interfaces (not yet released). + * Shall not be visible in public API, as it will be deleted after next GeoAPI release. + */ +@SuppressWarnings("doclint:missing") +public enum TemporalOperatorName { - AFTER, BEFORE, BEGINS, BEGUN_BY, CONTAINS, DURING, EQUALS, OVERLAPS, MEETS, ENDS, - OVERLAPPED_BY, MET_BY, ENDED_BY, ANY_INTERACTS; ++ AFTER, BEFORE(AFTER), BEGINS, BEGUN_BY(BEGINS), CONTAINS, DURING(CONTAINS), ++ EQUALS, OVERLAPS, MEETS, ENDS, ++ OVERLAPPED_BY(OVERLAPS), MET_BY(MEETS), ENDED_BY(ENDS), ANY_INTERACTS; ++ ++ static { ++ EQUALS.reversed = EQUALS; ++ ANY_INTERACTS.reversed = ANY_INTERACTS; ++ } ++ ++ private TemporalOperatorName reversed; ++ ++ private TemporalOperatorName() { ++ } ++ ++ private TemporalOperatorName(TemporalOperatorName r) { ++ reversed = r; ++ r.reversed = this; ++ } + + public String identifier() { + return name().toLowerCase(); + } ++ ++ public Optional<TemporalOperatorName> reversed() { ++ return Optional.of(reversed); ++ } +} diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultInstant.java index 0000000000,0eb5682cbe..7e4a105e37 mode 000000,100644..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 @@@ -1,0 -1,336 +1,335 @@@ + /* + * 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.Objects; + import java.util.Optional; + import java.time.Duration; + import java.time.DateTimeException; + import java.time.ZonedDateTime; + import java.time.temporal.Temporal; + import java.time.temporal.TemporalAmount; ++import org.opengis.temporal.TemporalPrimitive; + 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: -import org.opengis.temporal.Period; -import org.opengis.temporal.Instant; -import org.opengis.temporal.TemporalPrimitive; -import org.opengis.temporal.IndeterminateValue; -import org.opengis.filter.TemporalOperatorName; -import org.opengis.temporal.IndeterminatePositionException; ++// Specific to the main branch: ++import org.apache.sis.pending.geoapi.temporal.Period; ++import org.apache.sis.pending.geoapi.temporal.Instant; ++import org.apache.sis.pending.geoapi.temporal.IndeterminateValue; ++import org.apache.sis.pending.geoapi.temporal.TemporalOperatorName; + + + /** + * Default implementation of an instant as defined by ISO 19108. + * 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 extends TemporalObject implements Instant { + /** + * For cross-version compatibility. + */ + private static final long serialVersionUID = 3898772638524283287L; + + /** + * The constant for the "unknown" instant. + */ + static final DefaultInstant UNKNOWN = new DefaultInstant(null, IndeterminateValue.UNKNOWN); + + /** + * The temporal position as a date, time or date/time. + * May be {@code null} if {@link #indeterminate} is non-null and not "before" or "after". + */ + @SuppressWarnings("serial") // Standard implementations are serializable. + private final Temporal position; + + /** + * The indeterminate value, or {@code null} if none. + */ + private final IndeterminateValue indeterminate; + + /** + * Creates a new instant. + * + * @param position the temporal position, or {@code null} if unknown or now. + * @param indeterminate the indeterminate value, or {@code null} if none. + */ + DefaultInstant(final Temporal position, final IndeterminateValue indeterminate) { + this.position = position; + 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}. + * + * @return the date, time or position on the time-scale represented by this primitive. + */ + @Override + public final Temporal getPosition() { + if (indeterminate != IndeterminateValue.NOW) { + return position; + } + return ZonedDateTime.now(); + } + + /** + * Returns the reason why the temporal position is missing or inaccurate. + * + * @return the reason why the temporal position is missing or inaccurate. + */ + @Override + public final Optional<IndeterminateValue> getIndeterminatePosition() { + return Optional.ofNullable(indeterminate); + } + + /** + * Returns the distance from this instant to another instant or period. + * + * @param other the other object from which to measure the distance. + * @return the distance from this instant to another instant or period. + * @throws DateTimeException if the duration cannot be computed. + * @throws ArithmeticException if the calculation exceeds the integer capacity. + */ + @Override + public TemporalAmount distance(final TemporalPrimitive other) { + ArgumentChecks.ensureNonNull("other", other); + if (other instanceof Instant) { + return GeneralDuration.distance(this, (Instant) other, false, true); + } + if (other instanceof Period) { + final var p = (Period) other; + TemporalAmount t = GeneralDuration.distance(this, p.getBeginning(), false, false); + if (t == null) { + t = GeneralDuration.distance(this, p.getEnding(), true, false); + if (t == null) { + return Duration.ZERO; + } + } + return t; + } + throw new DateTimeException(Errors.format(Errors.Keys.UnsupportedType_1, other.getClass())); + } + + /** + * Determines the position of this instant 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 instant 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 relativeToInstant((Instant) other); + } + if (other instanceof Period) { + 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. + * + * @param other the other instant for which to determine the relative position. + * @return a temporal operator which is true when evaluated between this primitive and the other primitive. + * @throws DateTimeException if the temporal objects cannot be compared. + */ + @SuppressWarnings({"rawtypes", "unchecked"}) // See end of method. + private TemporalOperatorName relativeToInstant(final Instant other) { + boolean canTestBefore = true; + boolean canTestAfter = true; + boolean canTestEqual = true; + if (indeterminate != null && indeterminate != IndeterminateValue.NOW) { + canTestBefore = (indeterminate == IndeterminateValue.BEFORE); + canTestAfter = (indeterminate == IndeterminateValue.AFTER); + canTestEqual = false; + } + final IndeterminateValue oip = other.getIndeterminatePosition().orElse(null); + if (oip != null) { + if (oip != IndeterminateValue.NOW) { + canTestBefore &= (oip == IndeterminateValue.AFTER); + canTestAfter &= (oip == IndeterminateValue.BEFORE); + canTestEqual = false; + } else if (indeterminate == IndeterminateValue.NOW) { + return TemporalOperatorName.EQUALS; + } + } + cmp: if (canTestBefore | canTestAfter | canTestEqual) { + final Temporal t1; // Same as `this.position` except if "now". + final Temporal t2; // Position of the other instant. + final TimeMethods<?> comparators; // The "is before", "is after" and "is equal" methods to invoke. + /* + * First, resolve the case when the indeterminate value is "now". Do not invoke `getPosition()` + * because the results could differ by a few nanoseconds when two "now" instants are compared, + * and also for getting a temporal object of the same type than the other instant. + */ + if (oip == IndeterminateValue.NOW) { + t1 = position; + if (t1 == null) break cmp; + comparators = TimeMethods.find(t1.getClass()); + t2 = comparators.now(); + } else { + t2 = other.getPosition(); + if (t2 == null) break cmp; + if (indeterminate == IndeterminateValue.NOW) { + comparators = TimeMethods.find(t2.getClass()); + t1 = comparators.now(); + } else { + t1 = position; + if (t1 == null) break cmp; + comparators = TimeMethods.find(Classes.findCommonClass(t1.getClass(), t2.getClass())); + } + } + // This is where the @SuppressWarnings(…) apply. + if (canTestBefore && ((TimeMethods) comparators).isBefore.test(t1, t2)) return TemporalOperatorName.BEFORE; + if (canTestAfter && ((TimeMethods) comparators).isAfter .test(t1, t2)) return TemporalOperatorName.AFTER; + if (canTestEqual && ((TimeMethods) comparators).isEqual .test(t1, t2)) return TemporalOperatorName.EQUALS; + } - throw new IndeterminatePositionException(Errors.format(Errors.Keys.IndeterminatePosition)); ++ throw new DateTimeException(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. + */ + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + if (object instanceof DefaultInstant) { + final var that = (DefaultInstant) object; + return indeterminate == that.indeterminate + && Objects.equals(position, that.position) + && equalIdentifiers(that); + } + return false; + } + + /** + * Computes a hash code value for this instant. + */ + @Override + public int hashCode() { + return Objects.hash(position, indeterminate); + } + + /** + * Returns a string representation of this instant. + * This is either the date, the indeterminate position (e.g., "now"), + * or a combination of both (e.g., "after 2000-01-01"). + */ + @Override + public String toString() { + final var s = new StringBuilder(); + if (indeterminate != null) { + s.append(indeterminate.identifier()); + if (position != null) { + s.append(' ').append(position); + } + } else { + s.append(position); // Should never be null. + } + return s.toString(); + } + } diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultPeriod.java index 0000000000,a5142cc6c0..0db49a9a5b mode 000000,100644..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 @@@ -1,0 -1,206 +1,206 @@@ + /* + * 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.Map; + import java.time.DateTimeException; + import java.time.temporal.TemporalAmount; ++import org.opengis.temporal.TemporalPrimitive; + 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; ++// Specific to the main branch: ++import org.apache.sis.pending.geoapi.temporal.Instant; ++import org.apache.sis.pending.geoapi.temporal.Period; ++import org.apache.sis.pending.geoapi.temporal.TemporalOperatorName; + + + /** + * 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 extends TemporalObject implements Period { + /** + * For cross-version compatibility. + */ + private static final long serialVersionUID = 3870895998810224339L; + + /** + * Bounds making this period. + */ + @SuppressWarnings("serial") // Default implementations are serializable. + private final Instant beginning, ending; + + /** + * Creates a new period with the given bounds. + */ + DefaultPeriod(final Instant beginning, final Instant ending) { + this.beginning = beginning; + this.ending = ending; + } + + /** + * Returns the beginning instant at which this period starts. + */ + @Override + public Instant getBeginning() { + return beginning; + } + + /** + * Returns the ending instant at which this period ends. + */ + @Override + public Instant getEnding() { + return ending; + } + + /** + * Returns the duration of this period. + */ + @Override + public TemporalAmount length() { + 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>}. + */ + @Override + public String toString() { + return beginning + "/" + ending; + } + + /** + * Hash code value of the time position. + */ + @Override + public int hashCode() { + return beginning.hashCode() + 37 * ending.hashCode(); + } + + /** + * Compares with given object for equality. + */ + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof DefaultPeriod) { + final var other = (DefaultPeriod) obj; + 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 --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultPeriodDuration.java index fb98bc56fe,c0372e7397..0ef273bdd6 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultPeriodDuration.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/DefaultPeriodDuration.java @@@ -20,13 -20,21 +20,16 @@@ import java.io.Serializable import java.time.temporal.TemporalAmount; import org.opengis.temporal.PeriodDuration; -// Specific to the geoapi-3.1 branch: -import java.time.temporal.Temporal; -import java.time.temporal.TemporalUnit; -import java.util.List; - /** - * Default implementation of GeoAPI period duration. This is a temporary class; - * GeoAPI temporal interfaces are expected to change a lot in a future revision. + * Default implementation of GeoAPI period duration. * * @author Martin Desruisseaux (Geomatys) + * + * @deprecated This is a temporary class for compatibility with GeoAPI 3.x only. + * It should disappear with GeoAPI 4.0. */ + @Deprecated @SuppressWarnings("serial") public final class DefaultPeriodDuration implements PeriodDuration, Serializable { /** diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/GeneralDuration.java index 0000000000,6cd1724710..894d4c6ac5 mode 000000,100644..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 @@@ -1,0 -1,370 +1,369 @@@ + /* + * 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.Optional; + import java.io.Serializable; + import java.time.Period; + import java.time.Duration; + import java.time.LocalTime; + import java.time.LocalDateTime; + import java.time.ZonedDateTime; + import java.time.DateTimeException; + import java.time.chrono.ChronoLocalDate; + import java.time.chrono.ChronoPeriod; + import java.time.temporal.ChronoField; + import java.time.temporal.ChronoUnit; + import java.time.temporal.Temporal; + import java.time.temporal.TemporalAmount; + import java.time.temporal.TemporalUnit; + import java.time.temporal.UnsupportedTemporalTypeException; + import org.apache.sis.pending.jdk.JDK18; + import org.apache.sis.util.privy.UnmodifiableArrayList; + import org.apache.sis.util.resources.Errors; + -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.temporal.Instant; -import org.opengis.temporal.IndeterminateValue; -import org.opengis.temporal.IndeterminatePositionException; ++// Specific to the main branch: ++import org.apache.sis.pending.geoapi.temporal.Instant; ++import org.apache.sis.pending.geoapi.temporal.IndeterminateValue; + + + /** + * A data type to be used for describing length or distance in the temporal dimension. + * This implementation combines {@link java.time.Period} with {@link java.time.Duration} + * for situations where both of them are needed together (which is not recommended). + * + * @author Martin Desruisseaux (Geomatys) + */ + public final class GeneralDuration implements TemporalAmount, Serializable { + /** + * For cross-version compatibility. + */ + private static final long serialVersionUID = -521478824158640275L; + + /** + * The period in numbers of years, months and days. + * Shall be non-null and non-zero. + */ + public final Period period; + + /** + * The time part of the period in numbers of hours, minutes and seconds. + * Shall be non-null, non-zero and less than one day. + */ + public final Duration time; + + /** + * Creates a new instance with the given parts. + * The two parts must be non-null and non-zero. + * + * @param period the period in numbers of years, months and days. + * @param time the time part of the period in numbers of hours, minutes and seconds. + */ + private GeneralDuration(final Period period, final Duration time) { + this.period = period; + 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 + * are sufficient, or an instance of {@code GeneralDuration} is last resort. + * + * @param text the text to parse. + * @return the parsed period and/or duration. + * @throws DateTimeParseException if the given text cannot be parsed. + * + * @see Period#parse(CharSequence) + * @see Duration#parse(CharSequence) + */ + public static TemporalAmount parse(final CharSequence text) { + char previousLetter = 0; + final int length = text.length(); + for (int i=0; i<length; i++) { + char c = text.charAt(i); + if (c >= 'a' && c <= 'z') c -= 'a' - 'A'; // Quick upper case, ASCII characters only. + if (c >= 'A' && c <= 'Z') { + if (c == 'T') { + if (previousLetter == 'P') { + return Duration.parse(text); + } + return of(Period.parse(text.subSequence(0, i)), + Duration.parse(new StringBuilder(length - i + 1).append('P').append(text, i, length))); + } + previousLetter = c; + } + } + return Period.parse(text); + } + + /** + * Returns the temporal position of the given instant if that position is determinate or is "now". + * Otherwise, throws an exception. If the position is "now", then this method returns {@code null} + * instead of fetching the current time in order to avoid mismatch when comparing two "now" values + * that are a few nanoseconds apart. + * + * @param t the instant for which to get the temporal position, or {@code null}. + * @return temporal position of the given instant, or {@code null} for "now". + * @throws DateTimeException if the given instant is null or its position is indeterminate. + */ + private static Temporal getDeterminatePosition(final Instant t) { + if (t != null) { + final Optional<IndeterminateValue> p = t.getIndeterminatePosition(); + if (p.isEmpty()) { + return t.getPosition(); + } + if (p.get() == IndeterminateValue.NOW) { + return null; // Avoid fetching the current time now. + } + } - throw new IndeterminatePositionException(Errors.format(Errors.Keys.IndeterminatePosition)); ++ throw new DateTimeException(Errors.format(Errors.Keys.IndeterminatePosition)); + } + + /** + * Returns the distance between the two given ISO 19108 instants. + * If the result is negative, then the return value depends on the {@code absolute} argument: + * If {@code true}, this method returns the absolute value. Otherwise, it returns {@code null}. + * + * <p>If everything else is equal, methods such as {@link ChronoLocalDate#until(ChronoLocalDate)} + * will be invoked on the {@code self} instance. It makes a difference in the type of the result. + * For computing a duration with arguments in the reverse order, the {@code negate} parameter can + * be set to {@code true}.</p> + * + * @param self the first instant from which to measure the distance. + * @param other the second instant from which to measure the distance. + * @param negate whether to negate the result. True for duration from {@code other} to {@code self}. + * @param absolute whether to return absolute value. If false, negative result is replaced by null. + * @return the distance, or {@code null} if the result is negative and {@code absolute} is false. + * @throws DateTimeException if the duration cannot be computed. + * @throws ArithmeticException if the calculation exceeds the integer capacity. + */ + static TemporalAmount distance(final Instant self, final Instant other, final boolean negate, final boolean absolute) { + /* + * Get the temporal value, or null if "now". Other indeterminate values cause an exception to be thrown. + * We use null for "now" instead of fetching the current time for two reasons: avoid mismatch by a few + * nanoseconds when comparing `t1` with `t2`, and for choosing a type compatible with the other instant. + */ + Temporal t1 = getDeterminatePosition(self); + Temporal t2 = getDeterminatePosition(other); + /* + * Ensures that the given objects both have a date part, or that none of them have a date part. + * Note that the "epoch day" field is supported by `LocalDate` as well as the dates with zone ID. + */ + final boolean hasDate = isSupportedByBoth(ChronoField.EPOCH_DAY, t1, t2); + /* + * If at least one date has a timezone, then we require that both dates have a timezone. + * It allows an unambiguous duration in number of days, without time-varying months or years. + * If one date has a timezone and the other does not, a `DateTimeException` will be thrown. + * + * Note 1: we could be lenient and handle the two dates as if they were local, ignoring the timezone. + * But we avoid false sense of accuracy for now. We may revisit this policy later if there is a need. + */ + if (t1 != null && t1.isSupported(ChronoField.OFFSET_SECONDS)) { + if (t2 == null) t2 = ZonedDateTime.now(); + final Duration p = Duration.between(t1, t2); + return (absolute || p.isNegative() == negate) ? p.abs() : null; + } + if (t2 != null && (!hasDate || t2.isSupported(ChronoField.OFFSET_SECONDS))) { + if (t1 == null) t1 = ZonedDateTime.now(); + final Duration p = Duration.between(t2, t1); // Negative of the result. + return (absolute || p.isNegative() != negate) ? p.abs() : null; + } + /* + * Ensure that the given temporal objects both have a time part, or none of them have a time part. + * If only one of them has a time part, we do not interpret the other one as an instant at midnight + * in order to avoid false sense of accuracy. + */ + final boolean hasTime = isSupportedByBoth(ChronoField.SECOND_OF_DAY, t1, t2); + if (t1 == null) t1 = LocalDateTime.now(); + if (t2 == null) t2 = LocalDateTime.now(); + ChronoLocalDate d1 = null, d2 = null; + if (hasDate) { + d1 = ChronoLocalDate.from(t1); + d2 = ChronoLocalDate.from(t2); + if (!absolute && (negate ? d1.isBefore(d2) : d1.isAfter(d2))) { + return null; // Stop early if we can. + } + } + /* + * Compute the duration in the time part. If negative (after negation if `negate` is true), + * then we add the necessary number of days to make it positive and remove the same number + * of days from the date. We adjust the date instead of the period computed by `d1.until(d2)` + * in order to have the correct adjustment for the variable number of days in each month. + */ + Duration time = Duration.ZERO; + if (hasTime) { + time = Duration.between(LocalTime.from(t1), LocalTime.from(t2)); + if (hasDate) { + final boolean isPositive = d1.isBefore(d2); + if (isPositive || d1.isAfter(d2)) { // Require the period to be non-zero. + if (isPositive ? time.isNegative() : JDK18.isPositive(time)) { + long n = time.toDays(); // Truncated toward 0. + if (isPositive) { + d2 = d2.plus (--n, ChronoUnit.DAYS); // `n` is negative. Reduces period by decreasing the ending. + } else { + d1 = d1.minus(++n, ChronoUnit.DAYS); // `n` is positive. Reduces period by increasing the beginning. + } + time = time.minusDays(n); // If negative, make positive. If positive, make negative. + } + } + } + } + /* + * Get the period for the date part, then combine with the time part if non-zero. + * The result shall be either positive or null. + */ + if (hasDate) { + ChronoPeriod period = d1.until(d2); + if (period.isZero()) { + if (time.isZero()) { + return period; + } + } else { + if (period.isNegative()) { + if (!(negate | absolute)) { // Equivalent to (!negate && !absolute). + return null; + } + period = period.negated(); + } else if (negate & !absolute) { // Equivalent to (negate && !absolute). + return null; + } + return time.isZero() ? period : new GeneralDuration(Period.from(period), time.abs()); + } + } + return (absolute || time.isNegative() == negate) ? time.abs() : null; + } + + /** + * Verifies if the given field is supported by both temporal objects. + * Either the field is supported by both objects, or either it is supported by none of them. + * If one object support the field and the other does not, the two objects are considered incompatible. + * At least one of the given objects shall be non-null. + * + * @param field the field to test. + * @param t1 the first temporal object, or {@code null} for "now". + * @param t2 the second temporal object, or {@code null} for "now". + * @return whether the given object is supported. + * @throws DateTimeException if the two objects are incompatible. + */ + private static boolean isSupportedByBoth(final ChronoField field, final Temporal t1, final Temporal t2) { + final boolean isSupported = (t1 != null ? t1 : t2).isSupported(field); + if (t1 != null && t2 != null && isSupported != t2.isSupported(field)) { + throw new DateTimeException(Errors.format(Errors.Keys.CanNotConvertFromType_2, + (isSupported ? t2 : t1).getClass(), + (isSupported ? t1 : t2).getClass())); + } + return isSupported; + } + + /** + * Returns the list of units that are supported by this implementation. + * This is the union of the units supported by the date part and by the time part. + */ + @Override + public List<TemporalUnit> getUnits() { + var prefix = period.getUnits(); + var suffix = time.getUnits(); + int i = prefix.size(); + var units = prefix.toArray(new TemporalUnit[i + suffix.size()]); + for (TemporalUnit unit : suffix) { + units[i++] = unit; + } + return UnmodifiableArrayList.wrap(units); + } + + /** + * Returns the value of the requested unit. + * + * @param unit the unit to get; + * @return value of the specified unit. + * @throws UnsupportedTemporalTypeException if the {@code unit} is not supported. + */ + @Override + public long get(final TemporalUnit unit) { + return (unit.isDateBased() ? period : time).get(unit); + } + + /** + * Adds this duration to the specified temporal object. + * + * @param temporal the temporal object to add the amount to. + * @return an object with the addition done. + */ + @Override + public Temporal addTo(Temporal temporal) { + return temporal.plus(period).plus(time); + } + + /** + * Subtracts this duration from the specified temporal object. + * + * @param temporal the temporal object to subtract the amount from. + * @return an object with the subtraction done. + */ + @Override + public Temporal subtractFrom(Temporal temporal) { + return temporal.minus(period).minus(time); + } + + /** + * Compares this duration with the given object for equality. + * + * @param object the object to compare with this duration. + * @return whether the two objects are equal. + */ + @Override + public boolean equals(final Object object) { + if (object instanceof GeneralDuration) { + final var other = (GeneralDuration) object; + return period.equals(other.period) && time.equals(other.time); + } + return false; + } + + /** + * Returns a hash code value for this duration. + * + * @return a hash code value for this duration. + */ + @Override + public int hashCode() { + return period.hashCode() * 31 + time.hashCode(); + } + + /** + * Returns this duration in ISO 8601 format. + */ + @Override + public String toString() { + return period.toString() + time.toString().substring(1); + } + } diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/TemporalObjects.java index 0000000000,027c086aa2..72877deddd mode 000000,100644..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 @@@ -1,0 -1,154 +1,154 @@@ + /* + * 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.Date; + import java.time.temporal.Temporal; + import org.opengis.temporal.TemporalPrimitive; + import org.apache.sis.util.ArgumentChecks; + import org.apache.sis.util.resources.Errors; + -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.temporal.IndeterminateValue; -import org.opengis.temporal.Instant; -import org.opengis.temporal.Period; ++// Specific to the main branch: ++import org.apache.sis.pending.geoapi.temporal.IndeterminateValue; ++import org.apache.sis.pending.geoapi.temporal.Instant; ++import org.apache.sis.pending.geoapi.temporal.Period; + + + /** + * Utilities related to ISO 19108 objects. + * + * @author Martin Desruisseaux (Geomatys) + * @author Guilhem Legal (Geomatys) + */ + public final class TemporalObjects { + /** + * Do not allow instantiation of this class. + */ + private TemporalObjects() { + } + + /** + * Creates an instant for the given Java temporal instant. + * + * @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 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 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 position, final IndeterminateValue indeterminate) { + if (indeterminate == IndeterminateValue.UNKNOWN) { + return DefaultInstant.UNKNOWN; + } + if (indeterminate == null || indeterminate == IndeterminateValue.BEFORE || indeterminate == IndeterminateValue.AFTER) { + ArgumentChecks.ensureNonNull("position", position); + } + return new DefaultInstant(position, indeterminate); + } + + /** + * Creates an instant for the given indeterminate value. + * The given value cannot be "before" or "after". + * + * @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 indeterminate) { + ArgumentChecks.ensureNonNull("indeterminate", indeterminate); + if (indeterminate == IndeterminateValue.BEFORE || indeterminate == IndeterminateValue.AFTER) { + throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "indeterminate", indeterminate)); + } + return (indeterminate == IndeterminateValue.UNKNOWN) ? DefaultInstant.UNKNOWN : new DefaultInstant(null, indeterminate); + } + + /** + * Creates a period for the given begin and end instant. + * + * @param beginning the begin instant (inclusive), or {@code null}. + * @param ending the end instant (exclusive), or {@code null}. + * @return the period, or {@code null} if both arguments are null. + */ + public static Period createPeriod(final Temporal beginning, final Temporal ending) { + if (beginning == null && ending == null) { + return null; + } + return new DefaultPeriod(createInstant(beginning), createInstant(ending)); + } + + /** + * Creates a period for the given begin and end instant. + * + * @param beginning the begin instant (inclusive), or {@code null}. + * @param ending the end instant (exclusive), or {@code null}. + * @return the period, or {@code null} if both arguments are null. + */ + public static Period createPeriod(Instant beginning, Instant ending) { + if (beginning == null && ending == null) { + return null; + } + if (beginning == null) beginning = DefaultInstant.UNKNOWN; + if (ending == null) ending = DefaultInstant.UNKNOWN; + return new DefaultPeriod(beginning, ending); + } + + /** + * Returns the given value as a temporal position, or {@code null} if not available. + * + * @param time the instant or period for which to get a date, or {@code null}. + * @return the temporal position, or {@code null} if indeterminate. + */ + public static Temporal getInstant(final TemporalPrimitive time) { + if (time instanceof Instant) { + return ((Instant) time).getPosition(); + } + return null; + } + + /** + * Infers a value from the instant or extent as a {@link Date} object. + * This method is used for compatibility with legacy API and may disappear in future SIS version. + * + * @param time the instant or period for which to get a date, or {@code null}. + * @return the requested time as a Java date, or {@code null} if none. + */ + public static Date getAnyDate(final TemporalPrimitive time) { + Temporal t = null; + if (time instanceof Instant) { + t = ((Instant) time).getPosition(); + } else if (time instanceof Period) { + final var p = (Period) time; + Instant i = p.getEnding(); // Should never be null, but we are paranoiac. + if (i == null || (t = i.getPosition()) == null) { + i = p.getBeginning(); + if (i != null) { + t = i.getPosition(); + } + } + } + return TemporalDate.toDate(t); + } + } diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TM_Primitive.java index d291a2adcf,4cd2880a96..cae2ac62a2 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TM_Primitive.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TM_Primitive.java @@@ -23,11 -23,11 +23,11 @@@ import org.opengis.temporal.TemporalPri import org.apache.sis.xml.privy.XmlUtilities; import org.apache.sis.xml.bind.Context; import org.apache.sis.xml.bind.gco.PropertyType; - import org.apache.sis.pending.temporal.TemporalUtilities; + import org.apache.sis.temporal.TemporalObjects; import org.apache.sis.util.resources.Errors; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.temporal.Period; +// Specific to the main branch: +import org.apache.sis.pending.geoapi.temporal.Period; /** diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TimePeriodBound.java index ab2ee6fb11,5b51345ec5..921666dc80 --- a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TimePeriodBound.java +++ b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/TimePeriodBound.java @@@ -23,6 -22,9 +22,9 @@@ import jakarta.xml.bind.annotation.XmlE import jakarta.xml.bind.annotation.XmlAttribute; import jakarta.xml.bind.annotation.XmlTransient; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.temporal.Instant; ++// Specific to the main branch: ++import org.apache.sis.pending.geoapi.temporal.Instant; + /** * The {@linkplain TimePeriod#begin begin} or {@linkplain TimePeriod#end end} position in diff --cc endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultCitationDateTest.java index c841f55160,55e52af59c..7c97bc963d --- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultCitationDateTest.java +++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultCitationDateTest.java @@@ -47,13 -47,14 +47,13 @@@ public final class DefaultCitationDateT */ @Test public void testCopyConstructor() { - final CitationDate original = new CitationDate() { - @Override public Date getDate() {return new Date(1305716658508L);} - @Override public DateType getDateType() {return DateType.CREATION;} + final var original = new CitationDate() { - @Override public Temporal getReferenceDate() {return Instant.ofEpochMilli(1305716658508L);} ++ @Override public Date getDate() {return new Date(1305716658508L);} + @Override public DateType getDateType() {return DateType.CREATION;} }; - final DefaultCitationDate copy = new DefaultCitationDate(original); + final var copy = new DefaultCitationDate(original); assertEquals(Instant.ofEpochMilli(1305716658508L), copy.getReferenceDate()); assertEquals(DateType.CREATION, copy.getDateType()); - assertFalse(copy.equals(original, ComparisonMode.STRICT)); // Opportunist test. - assertTrue (copy.equals(original, ComparisonMode.DEBUG)); + assertFalse(copy.equals(original, ComparisonMode.STRICT)); // Opportunist test. } } diff --cc endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultInstantTest.java index 0000000000,403defe9f3..4607c106c1 mode 000000,100644..100644 --- a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultInstantTest.java +++ b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultInstantTest.java @@@ -1,0 -1,215 +1,215 @@@ + /* + * 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.time.Period; + import java.time.Duration; + import java.time.LocalDate; + import java.time.LocalDateTime; + import java.time.ZonedDateTime; + import java.time.ZoneOffset; + import org.opengis.temporal.TemporalPrimitive; + -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.filter.TemporalOperatorName; -import org.opengis.temporal.IndeterminateValue; -import org.opengis.temporal.IndeterminatePositionException; ++// Specific to the main branch: ++import java.time.DateTimeException; ++import org.apache.sis.pending.geoapi.temporal.IndeterminateValue; ++import org.apache.sis.pending.geoapi.temporal.TemporalOperatorName; + + // Test dependencies + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.function.Executable; + import static org.junit.jupiter.api.Assertions.*; + import org.apache.sis.test.TestCase; + + + /** + * Tests the {@link DefaultInstant} class. + * + * @author Mehdi Sidhoum (Geomatys) + * @author Martin Desruisseaux (Geomatys) + */ + public final class DefaultInstantTest extends TestCase { + /** + * Creates a new test case. + */ + public DefaultInstantTest() { + } + + /** + * Tests {@link DefaultInstant#getPosition()}. + * Opportunistically tests {@link TemporalObjects#createInstant(Temporal)} too. + */ + @Test + public void testGetPosition() { + var date = LocalDate.of(2010, 5, 1); + var instant = TemporalObjects.createInstant(date); + assertEquals(date, instant.getPosition()); + } + + /** + * Test of equals and hash code methods. + */ + @Test + public void testEquals() { + var instant1 = new DefaultInstant(LocalDate.of(2000, 1, 1), null); + var instant2 = new DefaultInstant(LocalDate.of(1988, 1, 1), null); + var instant3 = new DefaultInstant(LocalDate.of(1988, 1, 1), null); + + assertNotEquals(instant1, instant2); + assertNotEquals(instant1.hashCode(), instant2.hashCode()); + + assertEquals(instant3, instant2); + assertEquals(instant3.hashCode(), instant2.hashCode()); + } + + /** + * Tests {@link DefaultInstant#toString()}. + */ + @Test + public void testToString() { + var date = LocalDate.of(2010, 5, 1); + assertEquals("2010-05-01", new DefaultInstant(date, null).toString()); + assertEquals("after 2010-05-01", new DefaultInstant(date, IndeterminateValue.AFTER).toString()); + } + + /** + * Tests {@link DefaultInstant#findRelativePosition(TemporalPrimitive)} between instants. + */ + @Test + public void testRelativePositionBetweenInstants() { + final var t1981 = new DefaultInstant(LocalDate.of(1981, 6, 5), null); + final var t2000 = new DefaultInstant(LocalDate.of(2000, 1, 1), null); + assertEquals(TemporalOperatorName.BEFORE, t1981.findRelativePosition(t2000)); + assertEquals(TemporalOperatorName.AFTER, t2000.findRelativePosition(t1981)); + assertEquals(TemporalOperatorName.EQUALS, t2000.findRelativePosition(t2000)); + } + + /** + * Tests {@link DefaultInstant#findRelativePosition(TemporalPrimitive)} between an instant and a period. + */ + @Test + public void testRelativePositionBetweenInstantAndPeriod() { + final var before = new DefaultInstant(LocalDate.of(1981, 1, 1), null); + final var begins = new DefaultInstant(LocalDate.of(1981, 6, 5), null); + final var during = new DefaultInstant(LocalDate.of(1990, 1, 1), null); + final var ends = new DefaultInstant(LocalDate.of(2000, 1, 1), null); + final var after = new DefaultInstant(LocalDate.of(2000, 1, 2), null); + final var period = new DefaultPeriod(begins, ends); + assertEquals(TemporalOperatorName.BEFORE, before.findRelativePosition(period)); + assertEquals(TemporalOperatorName.BEGINS, begins.findRelativePosition(period)); + assertEquals(TemporalOperatorName.DURING, during.findRelativePosition(period)); + assertEquals(TemporalOperatorName.ENDS, ends .findRelativePosition(period)); + assertEquals(TemporalOperatorName.AFTER, after .findRelativePosition(period)); + } + + /** + * Tests {@link DefaultInstant#findRelativePosition(TemporalPrimitive)} with indeterminate instants. + * The position tested are "before" and "after". + */ + @Test + public void testIndeterminatePosition() { + final var before2000 = new DefaultInstant(LocalDate.of(2000, 1, 1), IndeterminateValue.BEFORE); + final var after2010 = new DefaultInstant(LocalDate.of(2010, 1, 1), IndeterminateValue.AFTER); + final var before2020 = new DefaultInstant(LocalDate.of(2020, 1, 1), IndeterminateValue.BEFORE); + + assertEquals(TemporalOperatorName.BEFORE, before2000.findRelativePosition( after2010)); + assertEquals(TemporalOperatorName.AFTER, after2010.findRelativePosition(before2000)); + assertIndeterminate(() -> after2010.findRelativePosition(before2020)); + assertIndeterminate(() -> before2000.findRelativePosition(before2020)); + assertIndeterminate(() -> before2020.findRelativePosition(before2000)); + } + + /** + * Asserts that the result of the given comparison is indeterminate. + * + * @param c the comparison to perform. + */ + private static void assertIndeterminate(final Executable c) { - assertNotNull(assertThrows(IndeterminatePositionException.class, c).getMessage()); ++ assertNotNull(assertThrows(DateTimeException.class, c).getMessage()); + } + + /** + * Tests {@link DefaultInstant#distance(TemporalPrimitive)} between two locale dates. + */ + @Test + public void testDistanceBetweenLocalDates() { + final var t1981 = new DefaultInstant(LocalDate.of(1981, 6, 5), null); + final var t2000 = new DefaultInstant(LocalDate.of(2000, 8, 8), null); + final Period expected = Period.of(19, 2, 3); + assertEquals(expected, t1981.distance(t2000)); + assertEquals(expected, t2000.distance(t1981)); + assertEquals(Period.ZERO, t2000.distance(t2000)); + } + + /** + * Tests {@link DefaultInstant#distance(TemporalPrimitive)} between two dates with timezone. + */ + @Test + public void testDistanceBetweenZonedDates() { + final var t2000 = new DefaultInstant(ZonedDateTime.of(2000, 6, 5, 12, 4, 0, 0, ZoneOffset.UTC), null); + final var t2001 = new DefaultInstant(ZonedDateTime.of(2001, 6, 5, 14, 4, 0, 0, ZoneOffset.UTC), null); + final Duration expected = Duration.ofDays(365).plusHours(2); + assertEquals(expected, t2000.distance(t2001)); + assertEquals(expected, t2001.distance(t2000)); + assertEquals(Duration.ZERO, t2000.distance(t2000)); + } + + /** + * Tests {@link DefaultInstant#distance(TemporalPrimitive)} between two dates with times. + * The period cannot be expressed with standard {@link java.time} objects. + */ + @Test + public void testDistanceBetweenLocalDateTimes() { + final var t1 = new DefaultInstant(LocalDateTime.of(2000, 6, 5, 12, 4, 0, 0), null); + final var t3 = new DefaultInstant(LocalDateTime.of(2001, 6, 9, 14, 4, 0, 0), null); + final var t2 = new DefaultInstant(LocalDateTime.of(2001, 6, 9, 10, 4, 0, 0), null); + + Object expected = "P1Y4DT2H"; + assertEquals(expected, t1.distance(t3).toString()); + assertEquals(expected, t3.distance(t1).toString()); + assertEquals(Period.ZERO, t1.distance(t1)); + + expected = "P1Y3DT22H"; + assertEquals(expected, t1.distance(t2).toString()); + assertEquals(expected, t2.distance(t1).toString()); + assertEquals(Period.ZERO, t2.distance(t2)); + + expected = Duration.ofHours(4); + assertEquals(expected, t2.distance(t3)); + assertEquals(expected, t3.distance(t2)); + assertEquals(Period.ZERO, t3.distance(t3)); + } + + /** + * Tests {@link DefaultInstant#distance(TemporalPrimitive)} between an instant and a period. + */ + @Test + public void testDistanceWithPeriod() { + final var before = new DefaultInstant(LocalDate.of(1981, 1, 1), null); + final var begins = new DefaultInstant(LocalDate.of(1981, 6, 5), null); + final var during = new DefaultInstant(LocalDate.of(1990, 1, 1), null); + final var ends = new DefaultInstant(LocalDate.of(2000, 1, 1), null); + final var after = new DefaultInstant(LocalDate.of(2000, 1, 2), null); + final var period = new DefaultPeriod(begins, ends); + + assertEquals(Period.of(0, 5, 4), before.distance(period)); + assertEquals(Period.ZERO, begins.distance(period)); + assertEquals(Duration.ZERO, during.distance(period)); // `Duration` considered an implementation details. + assertEquals(Period.ZERO, ends .distance(period)); + assertEquals(Period.of(0, 0, 1), after .distance(period)); + } + } diff --cc endorsed/src/org.apache.sis.metadata/test/org/apache/sis/temporal/DefaultPeriodTest.java index 0000000000,9fbeb6c8f9..73829fe91b mode 000000,100644..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 @@@ -1,0 -1,128 +1,131 @@@ + /* + * 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.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; ++ ++// Specific to the main branch ++import org.apache.sis.pending.geoapi.temporal.TemporalOperatorName; + + + /** + * Tests the {@link DefaultPeriod} class. + * + * @author Mehdi Sidhoum (Geomatys) + * @author Martin Desruisseaux (Geomatys) + */ + public final class DefaultPeriodTest extends TestCase { + /** + * Creates a new test case. + */ + public DefaultPeriodTest() { + } + + /** + * Tests {@link DefaultPeriod#getBeginning()} and {@link DefaultPeriod#getEnding()}. + * Opportunistically tests {@link TemporalObjects#createPeriod(Temporal, Temporal)} too. + */ + @Test + public void testBounds() { + var beginning = LocalDate.of(2010, 5, 1); + var ending = LocalDate.of(2015, 8, 6); + var period = TemporalObjects.createPeriod(beginning, ending); + assertEquals(beginning, period.getBeginning().getPosition()); + assertEquals(ending, period.getEnding().getPosition()); + } + + /** + * Test of equals and hash code methods. + */ + @Test + public void testEquals() { + var p1 = TemporalObjects.createPeriod(LocalDate.of(2000, 1, 1), LocalDate.of(2010, 1, 1)); + var p2 = TemporalObjects.createPeriod(LocalDate.of(1988, 1, 1), LocalDate.of(2010, 1, 1)); + var p3 = TemporalObjects.createPeriod(LocalDate.of(1988, 1, 1), LocalDate.of(2010, 1, 1)); + + assertNotEquals(p1, p2); + assertNotEquals(p1.hashCode(), p2.hashCode()); + + assertEquals(p3, p2); + assertEquals(p3.hashCode(), p2.hashCode()); + } + + /** + * Tests {@link DefaultPeriod#toString()}. + */ + @Test + public void testToString() { + var p1 = TemporalObjects.createPeriod(LocalDate.of(2000, 1, 1), LocalDate.of(2010, 1, 1)); + assertEquals("2000-01-01/2010-01-01", p1.toString()); + } + + /** + * Tests {@link DefaultPeriod#length()}. + */ + @Test + public void testLength() { + var beginning = LocalDate.of(2010, 5, 1); + var ending = LocalDate.of(2015, 8, 6); + 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) ++ org.apache.sis.pending.geoapi.temporal.Period self, ++ org.apache.sis.pending.geoapi.temporal.Period other) + { + assertEquals(expected, self.findRelativePosition(other)); + assertEquals(expected.reversed().orElseThrow(), other.findRelativePosition(self)); + } + } diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java index c88ab0f08e,433f389f3f..abaa640846 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java @@@ -87,11 -87,8 +87,11 @@@ import org.apache.sis.util.iso.Types // Specific to the main and geoapi-3.1 branches: import org.opengis.referencing.ReferenceIdentifier; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.ObjectDomain; +// Specific to the main branch: +import org.opengis.referencing.ReferenceSystem; +import org.apache.sis.referencing.internal.ServicesForMetadata; - import org.apache.sis.util.privy.TemporalDate; +import org.apache.sis.referencing.factory.GeodeticObjectFactory; ++import org.apache.sis.temporal.TemporalDate; /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java index 87d21d75f6,455ce924a3..2ad8984f4c --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultTemporalDatum.java @@@ -35,10 -35,10 +35,10 @@@ import org.apache.sis.io.wkt.Formatter import org.apache.sis.io.wkt.FormattableObject; // Specific to the main and geoapi-3.1 branches: - import org.apache.sis.util.privy.TemporalDate; + import org.apache.sis.temporal.TemporalDate; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.metadata.Identifier; +// Specific to the main branch: +import org.opengis.referencing.ReferenceIdentifier; /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java index 4cd1a9ec72,7bf491410a..a0cfacca43 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java @@@ -121,12 -121,9 +121,12 @@@ import static org.apache.sis.util.privy import static org.apache.sis.util.Utilities.equalsIgnoreMetadata; import static org.apache.sis.referencing.internal.ServicesForMetadata.CONNECTION; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.metadata.Identifier; -import org.opengis.referencing.ObjectDomain; +// Specific to the main branch: ++import org.apache.sis.temporal.TemporalDate; +import org.apache.sis.referencing.internal.ServicesForMetadata; - import org.apache.sis.util.privy.TemporalDate; +import org.apache.sis.referencing.cs.DefaultParametricCS; +import org.apache.sis.referencing.datum.DefaultParametricDatum; +import org.apache.sis.referencing.factory.GeodeticObjectFactory; /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/AxisDirections.java index 3826d39454,29b606fbce..e0023115c4 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/AxisDirections.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/AxisDirections.java @@@ -71,90 -65,7 +71,98 @@@ public final class AxisDirections exten * * @see #isUserDefined(AxisDirection) */ - private static final int LAST_ORDINAL = UNSPECIFIED.ordinal(); + private static final int LAST_ORDINAL = DISPLAY_DOWN.ordinal(); + + /** + * Forward direction. + * For an observer at the centre of the object this is will be towards its front, bow or nose. + * Added in ISO 19111:2019 (was not in ISO 19111:2007). + */ + @UML(identifier="forward", obligation=CONDITIONAL, specification=ISO_19111) + public static final AxisDirection FORWARD = AxisDirection.valueOf("FORWARD"); + + /** + * Starboard direction. + * For an observer at the centre of the object this will be towards its right. + * Added in ISO 19111:2019 (was not in ISO 19111:2007). + */ + @UML(identifier="starboard", obligation=CONDITIONAL, specification=ISO_19111) + public static final AxisDirection STARBOARD = AxisDirection.valueOf("STARBOARD"); + + /** + * Port direction. + * For an observer at the centre of the object this will be towards its left. + * Added in ISO 19111:2019 (was not in ISO 19111:2007). + */ + @UML(identifier="port", obligation=CONDITIONAL, specification=ISO_19111) + public static final AxisDirection PORT = AxisDirection.valueOf("PORT"); + + /** + * Direction of geographic angles (bearing). + * Added in ISO 19111:2019 (was not in ISO 19111:2007). + */ + @UML(identifier="clockwise", obligation=CONDITIONAL, specification=ISO_19111) + public static final AxisDirection CLOCKWISE = AxisDirection.valueOf("CLOCKWISE"); + + /** + * Direction of arithmetic angles. Used in polar coordinate systems. + * Added in ISO 19111:2019 (was not in ISO 19111:2007). + */ + @UML(identifier="counterClockwise", obligation=CONDITIONAL, specification=ISO_19111) + public static final AxisDirection COUNTER_CLOCKWISE = AxisDirection.valueOf("COUNTER_CLOCKWISE"); + + /** + * Distance from the origin in a polar coordinate system. + * Added in ISO 19111:2019 (was not in ISO 19111:2007). + */ + @UML(identifier="awayFrom", obligation=CONDITIONAL, specification=ISO_19111) + public static final AxisDirection AWAY_FROM = AxisDirection.valueOf("AWAY_FROM"); + ++ /** ++ * Distance toward the origin. ++ * Added in ISO 19111:2019 (was not in ISO 19111:2007). ++ */ ++ @UML(identifier="towards", obligation=CONDITIONAL, specification=ISO_19111) ++ public static final AxisDirection TOWARDS = AxisDirection.valueOf("TOWARDS"); ++ + /** + * Axis positive direction is unspecified. + */ + @UML(identifier="unspecified", obligation=CONDITIONAL, specification=ISO_19111) + public static final AxisDirection UNSPECIFIED = AxisDirection.valueOf("UNSPECIFIED"); + + /** + * For each direction, the opposite direction. + * This map shall be immutable after construction. + */ + private static final Map<AxisDirection,AxisDirection> OPPOSITES = new HashMap<>(24); + static { + put(UNSPECIFIED, UNSPECIFIED); + put(NORTH, SOUTH); + put(NORTH_NORTH_EAST, SOUTH_SOUTH_WEST); + put(NORTH_EAST, SOUTH_WEST); + put(EAST_NORTH_EAST, WEST_SOUTH_WEST); + put(EAST, WEST); + put(EAST_SOUTH_EAST, WEST_NORTH_WEST); + put(SOUTH_EAST, NORTH_WEST); + put(SOUTH_SOUTH_EAST, NORTH_NORTH_WEST); + put(UP, DOWN); + put(FUTURE, PAST); + put(COLUMN_POSITIVE, COLUMN_NEGATIVE); + put(ROW_POSITIVE, ROW_NEGATIVE); + put(DISPLAY_RIGHT, DISPLAY_LEFT); + put(DISPLAY_UP, DISPLAY_DOWN); + put(COUNTER_CLOCKWISE, CLOCKWISE); ++ put(AWAY_FROM, TOWARDS); + } + + /** + * Stores the given directions in the {@link #OPPOSITES} array. + */ + private static void put(final AxisDirection dir, final AxisDirection opposite) { + OPPOSITES.put(dir, opposite); + OPPOSITES.put(opposite, dir); + } /** * Proposed abbreviations for some axis directions. diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java index 42c5ae51e6,778b40195b..fefbca7818 --- a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java +++ b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/privy/GeodeticObjectBuilder.java @@@ -61,8 -61,8 +61,8 @@@ import org.apache.sis.referencing.cs.Ax import org.apache.sis.referencing.internal.Resources; import org.apache.sis.parameter.Parameters; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.ObjectDomain; +// Specific to the main branch: - import org.apache.sis.util.privy.TemporalDate; ++import org.apache.sis.temporal.TemporalDate; /** diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CommonCRSTest.java index e312138ac2,296cb93297..1373e98bd9 --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CommonCRSTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/CommonCRSTest.java @@@ -52,11 -52,11 +52,11 @@@ import static org.apache.sis.test.Asser import static org.apache.sis.test.TestUtilities.*; // Specific to the main and geoapi-3.1 branches: - import org.apache.sis.util.privy.TemporalDate; + import org.apache.sis.temporal.TemporalDate; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.datum.RealizationMethod; -import static org.opengis.test.Assertions.assertAxisDirectionsEqual; +// Specific to the main branch: +import org.opengis.referencing.datum.VerticalDatumType; +import static org.apache.sis.test.GeoapiAssert.assertAxisDirectionsEqual; /** diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultTemporalDatumTest.java index adb23afaff,dc6845ddef..96a022b0fe --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultTemporalDatumTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/datum/DefaultTemporalDatumTest.java @@@ -35,11 -35,12 +35,11 @@@ import static org.apache.sis.test.TestU import static org.apache.sis.test.TestUtilities.getScope; // Specific to the main and geoapi-3.1 branches: - import org.apache.sis.util.privy.TemporalDate; + import org.apache.sis.temporal.TemporalDate; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import static org.opengis.referencing.ObjectDomain.*; -import static org.opengis.referencing.IdentifiedObject.*; -import static org.opengis.test.Assertions.assertIdentifierEquals; +// Specific to the main branch: +import static org.opengis.referencing.ReferenceSystem.*; +import static org.apache.sis.test.GeoapiAssert.assertIdentifierEquals; /** diff --cc endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/AxisDirectionsTest.java index f50eb7f5f9,92f84c5ac2..29672463ff --- a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/AxisDirectionsTest.java +++ b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/privy/AxisDirectionsTest.java @@@ -32,12 -32,6 +32,13 @@@ import org.apache.sis.referencing.cs.Ha import org.apache.sis.referencing.cs.HardCodedCS; import org.apache.sis.test.TestCase; +// Specific to the main branch: ++import static org.apache.sis.referencing.privy.AxisDirections.TOWARDS; +import static org.apache.sis.referencing.privy.AxisDirections.AWAY_FROM; +import static org.apache.sis.referencing.privy.AxisDirections.CLOCKWISE; +import static org.apache.sis.referencing.privy.AxisDirections.COUNTER_CLOCKWISE; +import static org.apache.sis.referencing.privy.AxisDirections.UNSPECIFIED; + /** * Tests the {@link AxisDirections} class. diff --cc endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java index c39a324c08,e8cdfc64e5..9f3be45a8a --- a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java +++ b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/CRSBuilder.java @@@ -59,9 -59,8 +59,9 @@@ import org.apache.sis.util.resources.Er import org.apache.sis.measure.NumberRange; import org.apache.sis.measure.Units; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.referencing.operation.CoordinateOperationFactory; +// Specific to the main branch: ++import org.apache.sis.temporal.TemporalDate; +import org.apache.sis.referencing.factory.GeodeticObjectFactory; - import org.apache.sis.util.privy.TemporalDate; /** diff --cc endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Copyright.java index 7223b7a6f6,88bc82ca4e..0697471ebc --- a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Copyright.java +++ b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Copyright.java @@@ -39,16 -39,11 +39,16 @@@ import org.opengis.metadata.constraint. import org.opengis.util.InternationalString; import org.apache.sis.util.iso.Types; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.metadata.citation.Party; -import org.opengis.metadata.citation.Responsibility; -import org.opengis.metadata.identification.BrowseGraphic; -import org.apache.sis.util.SimpleInternationalString; +// Specific to the main branch: +import java.util.Date; +import org.opengis.metadata.citation.Contact; +import org.opengis.metadata.citation.Series; +import org.opengis.metadata.citation.ResponsibleParty; - import org.apache.sis.util.privy.TemporalDate; +import org.apache.sis.metadata.iso.citation.AbstractParty; +import org.apache.sis.metadata.iso.citation.DefaultCitation; +import org.apache.sis.metadata.iso.citation.DefaultResponsibility; +import org.apache.sis.metadata.iso.constraint.DefaultConstraints; ++import org.apache.sis.temporal.TemporalDate; /**