This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 2448d007cd Add converters between strings to various kinds of
`java.time` objects. This is needed by `SQLStore` when a column is mapped to
e.g. `LocalDate`.
2448d007cd is described below
commit 2448d007cd4d2ec3505abdf18510c230c20af37a
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Tue Oct 22 17:53:50 2024 +0200
Add converters between strings to various kinds of `java.time` objects.
This is needed by `SQLStore` when a column is mapped to e.g. `LocalDate`.
---
.../org/apache/sis/feature/FeatureOperations.java | 7 +-
.../apache/sis/feature/StringJoinOperation.java | 14 +++-
.../org/apache/sis/metadata/sql/privy/Syntax.java | 2 +-
.../apache/sis/storage/sql/feature/Analyzer.java | 2 +-
.../apache/sis/storage/sql/feature/Database.java | 1 -
.../sis/storage/DataStoreContentException.java | 2 +-
.../src/org.apache.sis.util/main/module-info.java | 11 +++
.../apache/sis/converter/ConverterRegistry.java | 12 ++--
.../org/apache/sis/converter/DateConverter.java | 51 ++++++++-----
.../org/apache/sis/converter/InstantConverter.java | 83 ++++++++++++++++++++++
.../org/apache/sis/converter/StringConverter.java | 82 +++++++++++++++++++++
.../main/org/apache/sis/math/FunctionProperty.java | 4 +-
12 files changed, 234 insertions(+), 37 deletions(-)
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java
index f88ee704ac..4ab41f8df7 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/FeatureOperations.java
@@ -24,7 +24,6 @@ import org.opengis.util.InternationalString;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.Static;
-import org.apache.sis.util.UnconvertibleObjectException;
import org.apache.sis.util.collection.WeakHashSet;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.privy.Strings;
@@ -199,17 +198,15 @@ public final class FeatureOperations extends Static {
* @param suffix characters to use at the end of the
concatenated string, or {@code null} if none.
* @param singleAttributes identification of the single attributes (or
operations producing attributes) to concatenate.
* @return an operation which concatenates the string representations of
all referenced single property values.
- * @throws UnconvertibleObjectException if at least one of the given
{@code singleAttributes} uses a
- * {@linkplain DefaultAttributeType#getValueClass() value class}
which is not convertible from a {@link String}.
* @throws IllegalArgumentException if {@code singleAttributes} is an
empty sequence, or contains a property which
* is neither an {@code AttributeType} or an {@code Operation}
computing an attribute, or an attribute has
- * a {@linkplain DefaultAttributeType#getMaximumOccurs() maximum
number of occurrences} greater than 1.
+ * a {@linkplain DefaultAttributeType#getMaximumOccurs() maximum
number of occurrences} greater than 1, or
+ * uses a {@linkplain DefaultAttributeType#getValueClass() value
class} not convertible from a {@link String}.
*
* @see <a href="https://en.wikipedia.org/wiki/Compound_key">Compound key
on Wikipedia</a>
*/
public static Operation compound(final Map<String,?> identification, final
String delimiter,
final String prefix, final String suffix, final PropertyType...
singleAttributes)
- throws UnconvertibleObjectException
{
ArgumentChecks.ensureNonEmpty("delimiter", delimiter);
if (delimiter.indexOf(StringJoinOperation.ESCAPE) >= 0) {
diff --git
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java
index 7b075094e9..b702dc10e9 100644
---
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java
+++
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/feature/StringJoinOperation.java
@@ -176,12 +176,14 @@ final class StringJoinOperation extends AbstractOperation
{
* It is caller's responsibility to ensure that {@code delimiter} and
{@code singleAttributes} are not null.
* This private constructor does not verify that condition on the
assumption that the public API did.
*
+ * @throws UnconvertibleObjectException if at least one attributes is not
convertible from a string.
+ * @throws IllegalArgumentException if the operation failed for another
reason.
+ *
* @see FeatureOperations#compound(Map, String, String, String,
PropertyType...)
*/
@SuppressWarnings({"rawtypes", "unchecked"})
// Generic array creation.
StringJoinOperation(final Map<String,?> identification, final String
delimiter,
final String prefix, final String suffix, final PropertyType[]
singleAttributes)
- throws UnconvertibleObjectException
{
super(identification);
attributeNames = new String[singleAttributes.length];
@@ -234,8 +236,14 @@ final class StringJoinOperation extends AbstractOperation {
* We need only their names and how to convert from String to
their values.
*/
attributeNames[i] = name.toString();
- ObjectConverter<? super String, ?> converter =
ObjectConverters.find(
- String.class, ((AttributeType<?>)
propertyType).getValueClass());
+ final Class<?> valueClass = ((AttributeType<?>)
propertyType).getValueClass();
+ ObjectConverter<? super String, ?> converter;
+ try {
+ converter = ObjectConverters.find(String.class, valueClass);
+ } catch (UnconvertibleObjectException e) {
+ throw new
UnconvertibleObjectException(Resources.forProperties(identification)
+ .getString(Resources.Keys.IllegalPropertyType_2, name,
valueClass), e);
+ }
if (isAssociation) {
converter = new ForFeature(converter);
}
diff --git
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Syntax.java
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Syntax.java
index ab3e64bac5..ef21416737 100644
---
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Syntax.java
+++
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/privy/Syntax.java
@@ -51,7 +51,7 @@ public class Syntax {
* The string that can be used to escape wildcard characters.
* This is the value returned by {@link
DatabaseMetaData#getSearchStringEscape()}.
*/
- final String escape;
+ protected final String escape;
/**
* Creates a new {@code Syntax} initialized from the given database
metadata.
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Analyzer.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Analyzer.java
index e38ddd9b51..645ea0bd47 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Analyzer.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Analyzer.java
@@ -165,7 +165,7 @@ public final class Analyzer {
* Finds the keyword used for identifying tables and views.
* Derby, HSQLDB and PostgreSQL uses the "TABLE" type, but H2 uses
"BASE TABLE".
*/
- final Set<String> types = new HashSet<>(4);
+ final var types = new HashSet<String>(4);
try (ResultSet reflect = metadata.getTableTypes()) {
while (reflect.next()) {
final String type = reflect.getString(Reflection.TABLE_TYPE);
diff --git
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java
index 6d9a1f9593..b09cfed294 100644
---
a/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java
+++
b/endorsed/src/org.apache.sis.storage.sql/main/org/apache/sis/storage/sql/feature/Database.java
@@ -304,7 +304,6 @@ public class Database<G> extends Syntax {
* @return names of the standard tables defined by the spatial schema.
*/
final Set<String> detectSpatialSchema(final DatabaseMetaData metadata,
final String[] tableTypes) throws SQLException {
- final String escape = metadata.getSearchStringEscape();
/*
* The following tables are defined by ISO 19125 / OGC Simple feature
access part 2.
* Note that the standard specified those names in upper-case letters,
which is also
diff --git
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStoreContentException.java
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStoreContentException.java
index 3528b8ec0d..cec525b44e 100644
---
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStoreContentException.java
+++
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/DataStoreContentException.java
@@ -20,7 +20,7 @@ import java.util.Locale;
/**
- * Thrown when a store cannot be read because the stream contains invalid data.
+ * Thrown when a store cannot be read because the stream or database contains
invalid data.
* It may be for example a logical inconsistency, or a reference not found,
* or an unsupported file format version, <i>etc.</i>
*
diff --git a/endorsed/src/org.apache.sis.util/main/module-info.java
b/endorsed/src/org.apache.sis.util/main/module-info.java
index 040cfe0af5..9264ad41fe 100644
--- a/endorsed/src/org.apache.sis.util/main/module-info.java
+++ b/endorsed/src/org.apache.sis.util/main/module-info.java
@@ -45,6 +45,15 @@ module org.apache.sis.util {
org.apache.sis.converter.StringConverter.Double,
org.apache.sis.converter.StringConverter.BigInteger,
org.apache.sis.converter.StringConverter.BigDecimal,
+ org.apache.sis.converter.StringConverter.Instant,
+ org.apache.sis.converter.StringConverter.ZonedDateTime,
+ org.apache.sis.converter.StringConverter.OffsetDateTime,
+ org.apache.sis.converter.StringConverter.LocalDateTime,
+ org.apache.sis.converter.StringConverter.LocalDate,
+ org.apache.sis.converter.StringConverter.LocalTime,
+ org.apache.sis.converter.StringConverter.Year,
+ org.apache.sis.converter.StringConverter.YearMonth,
+ org.apache.sis.converter.StringConverter.MonthDay,
org.apache.sis.converter.StringConverter.Boolean,
org.apache.sis.converter.StringConverter.Locale,
org.apache.sis.converter.StringConverter.Charset,
@@ -72,6 +81,8 @@ module org.apache.sis.util {
org.apache.sis.converter.DateConverter.Long,
org.apache.sis.converter.DateConverter.SQL,
org.apache.sis.converter.DateConverter.Timestamp,
+ org.apache.sis.converter.DateConverter.Instant,
+ org.apache.sis.converter.InstantConverter.Date,
org.apache.sis.converter.CollectionConverter.List,
org.apache.sis.converter.CollectionConverter.Set,
org.apache.sis.converter.FractionConverter,
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/converter/ConverterRegistry.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/converter/ConverterRegistry.java
index 487fe9ce8b..8fc1453f9e 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/converter/ConverterRegistry.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/converter/ConverterRegistry.java
@@ -245,7 +245,7 @@ public class ConverterRegistry {
* ConverterRegistry), unwraps it and registers its component
individually.
*/
if (converter instanceof FallbackConverter<?,?>) {
- final FallbackConverter<S,T> fc = (FallbackConverter<S,T>)
converter;
+ final var fc = (FallbackConverter<S,T>) converter;
register(fc.primary);
register(fc.fallback);
return;
@@ -282,12 +282,14 @@ public class ConverterRegistry {
*/
continue;
}
- if (i.getName().startsWith("java.lang.constant")) {
+ switch (i.getPackageName()) {
/*
- * The Constable and ConstantDesc interfaces (introduced
in Java 12)
- * are internal mechanic for handling byte codes.
+ * The Constable and ConstantDesc interfaces (introduced
in Java 12) are internal mechanic
+ * for handling byte codes. The temporal interfaces are
unusual in that users are advised
+ * to use a specific implementation class instead of the
interface.
*/
- continue;
+ case "java.lang.constant":
+ case "java.time.temporal": continue;
}
if (Cloneable.class.isAssignableFrom(i)) {
/*
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/converter/DateConverter.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/converter/DateConverter.java
index da07589113..a14b1cfe57 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/converter/DateConverter.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/converter/DateConverter.java
@@ -21,18 +21,13 @@ import java.util.Set;
import java.util.EnumSet;
import org.apache.sis.util.ObjectConverter;
import org.apache.sis.math.FunctionProperty;
+import org.apache.sis.util.UnconvertibleObjectException;
/**
* Handles conversions from {@link Date} to various objects.
- *
- * <h2>String representation</h2>
- * There is currently no converter between {@link String} and {@link
java.util.Date} because the
- * date format is not yet defined (we are considering the ISO format for a
future SIS version).
- *
- * <h2>Special cases</h2>
- * The converter from dates to timestamps is not injective, because the same
date could be mapped
- * to many timestamps since timestamps have an additional nanoseconds field.
+ * Note that there is no converter between {@link String} and {@link
java.util.Date}.
+ * The {@link java.time.Instant} class should be used instead.
*
* <h2>Immutability and thread safety</h2>
* This base class and all inner classes are immutable, and thus inherently
thread-safe.
@@ -61,12 +56,14 @@ abstract class DateConverter<T> extends
SystemConverter<Date,T> {
}
/**
- * Returns the function properties.
+ * Returns the function properties. The function from {@code Date}
instances to {@code Timestamp} or
+ * {@code Instant} instances is <em>injective</em> because each instant is
either unrelated to dates
+ * (if the instant contains a nanosecond field), or is the output of
exactly one {@code Date} with
+ * nanoseconds assumed to be zero.
*/
@Override
public Set<FunctionProperty> properties() {
- return EnumSet.of(FunctionProperty.SURJECTIVE,
FunctionProperty.ORDER_PRESERVING,
- FunctionProperty.INVERTIBLE);
+ return EnumSet.of(FunctionProperty.INJECTIVE,
FunctionProperty.ORDER_PRESERVING, FunctionProperty.INVERTIBLE);
}
/**
@@ -171,12 +168,30 @@ abstract class DateConverter<T> extends
SystemConverter<Date,T> {
}
}
- /*
- * We do not yet provide converter to java.time.Instant. If we do so, we
need to create an InstantConverter class
- * doing the inverse conversion. Reminder: java.sql.Date and
java.sql.Time are not convertible to Instant (their
- * Date.toInstant() method throws UnsupportedOperationException), but
java.sql.Timestamp is.
- *
- * If conversion to/from java.time.Instant is added, see if some code can
be shared with
- * org.apache.sis.filter.ComparisonFilter.
+ /**
+ * From {@code Date} to {@code Instant}.
*/
+ public static final class Instant extends DateConverter<java.time.Instant>
{
+ private static final long serialVersionUID = 5727173560137117677L;
+
+ static final Instant INSTANCE = new Instant(); // Invoked by
ServiceLoader when using module-path.
+ public static Instant provider() {
+ return INSTANCE;
+ }
+
+ public Instant() { // Instantiated by
ServiceLoader when using class-path.
+ super(java.time.Instant.class);
+ inverse = InstantConverter.Date.INSTANCE;
+ }
+
+ @Override public java.time.Instant apply(final Date source) {
+ if (source != null) try {
+ return source.toInstant();
+ } catch (UnsupportedOperationException e) {
+ // Thrown by `java.sql.Date` and `java.sql.Time`, but not
`java.sql.Timestamp`.
+ throw new
UnconvertibleObjectException(formatErrorMessage(source), e);
+ }
+ return null;
+ }
+ }
}
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/converter/InstantConverter.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/converter/InstantConverter.java
new file mode 100644
index 0000000000..d49b6c9bf0
--- /dev/null
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/converter/InstantConverter.java
@@ -0,0 +1,83 @@
+/*
+ * 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.converter;
+
+import java.util.Set;
+import java.util.EnumSet;
+import java.time.Instant;
+import org.apache.sis.util.ObjectConverter;
+import org.apache.sis.math.FunctionProperty;
+
+
+/**
+ * Handles conversions from {@link Instant} to various objects.
+ *
+ * <h2>Immutability and thread safety</h2>
+ * This base class and all inner classes are immutable, and thus inherently
thread-safe.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ *
+ * @param <T> the base type of converted objects.
+ */
+abstract class InstantConverter<T> extends SystemConverter<Instant,T> {
+ /**
+ * For cross-version compatibility.
+ */
+ private static final long serialVersionUID = -7219681557586687605L;
+
+ /**
+ * Creates a converter for the given target type.
+ * Subclasses must initialize {@link #inverse}.
+ */
+ InstantConverter(final Class<T> targetClass) {
+ super(Instant.class, targetClass);
+ }
+
+ /**
+ * Returns the function properties. The function is <em>surjective</em>
because any {@code Date} instances
+ * can be created from one or many {@code Instant} instances. The same
date may be created from many instants
+ * because the nanosecond field is dropped.
+ */
+ @Override
+ public Set<FunctionProperty> properties() {
+ return EnumSet.of(FunctionProperty.SURJECTIVE,
FunctionProperty.ORDER_PRESERVING, FunctionProperty.INVERTIBLE);
+ }
+
+ /**
+ * From {@code Instant} to {@code Date}.
+ */
+ public static final class Date extends InstantConverter<java.util.Date> {
+ private static final long serialVersionUID = -9192665378798185400L;
+
+ static final Date INSTANCE = new Date(); // Invoked by
ServiceLoader when using module-path.
+ public static Date provider() {
+ return INSTANCE;
+ }
+
+ public Date() { // Instantiated by
ServiceLoader when using class-path.
+ super(java.util.Date.class);
+ }
+
+ @Override public ObjectConverter<java.util.Date, Instant> inverse() {
+ return DateConverter.Instant.INSTANCE;
+ }
+
+ @Override public java.util.Date apply(final Instant source) {
+ return (source != null) ? java.util.Date.from(source) : null;
+ }
+ }
+}
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/converter/StringConverter.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/converter/StringConverter.java
index 73f5eae546..98227e9dfc 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/converter/StringConverter.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/converter/StringConverter.java
@@ -19,6 +19,7 @@ package org.apache.sis.converter;
import java.util.Set;
import java.util.EnumSet;
import java.util.IllformedLocaleException;
+import java.time.format.DateTimeParseException;
import java.nio.charset.UnsupportedCharsetException;
import java.net.URISyntaxException;
import java.net.MalformedURLException;
@@ -232,6 +233,87 @@ abstract class StringConverter<T> extends
SystemConverter<String, T> {
}
}
+ public static final class Instant extends
StringConverter<java.time.Instant> {
+ private static final long serialVersionUID = -786622578610861924L;
+ public Instant() {super(java.time.Instant.class);}
+
+ @Override java.time.Instant doConvert(String source) throws
DateTimeParseException {
+ return java.time.Instant.parse(source);
+ }
+ }
+
+ public static final class ZonedDateTime extends
StringConverter<java.time.ZonedDateTime> {
+ private static final long serialVersionUID = 4547600422615778462L;
+ public ZonedDateTime() {super(java.time.ZonedDateTime.class);}
+
+ @Override java.time.ZonedDateTime doConvert(String source) throws
DateTimeParseException {
+ return java.time.ZonedDateTime.parse(source);
+ }
+ }
+
+ public static final class OffsetDateTime extends
StringConverter<java.time.OffsetDateTime> {
+ private static final long serialVersionUID = 6438936715171368273L;
+ public OffsetDateTime() {super(java.time.OffsetDateTime.class);}
+
+ @Override java.time.OffsetDateTime doConvert(String source) throws
DateTimeParseException {
+ return java.time.OffsetDateTime.parse(source);
+ }
+ }
+
+ public static final class LocalDateTime extends
StringConverter<java.time.LocalDateTime> {
+ private static final long serialVersionUID = 4020225109842204445L;
+ public LocalDateTime() {super(java.time.LocalDateTime.class);}
+
+ @Override java.time.LocalDateTime doConvert(String source) throws
DateTimeParseException {
+ return java.time.LocalDateTime.parse(source);
+ }
+ }
+
+ public static final class LocalDate extends
StringConverter<java.time.LocalDate> {
+ private static final long serialVersionUID = -2160961842632015681L;
+ public LocalDate() {super(java.time.LocalDate.class);}
+
+ @Override java.time.LocalDate doConvert(String source) throws
DateTimeParseException {
+ return java.time.LocalDate.parse(source);
+ }
+ }
+
+ public static final class LocalTime extends
StringConverter<java.time.LocalTime> {
+ private static final long serialVersionUID = -4872647331214579728L;
+ public LocalTime() {super(java.time.LocalTime.class);}
+
+ @Override java.time.LocalTime doConvert(String source) throws
DateTimeParseException {
+ return java.time.LocalTime.parse(source);
+ }
+ }
+
+ public static final class Year extends StringConverter<java.time.Year> {
+ private static final long serialVersionUID = 9014595771888427112L;
+ public Year() {super(java.time.Year.class);}
+
+ @Override java.time.Year doConvert(String source) throws
DateTimeParseException {
+ return java.time.Year.parse(source);
+ }
+ }
+
+ public static final class YearMonth extends
StringConverter<java.time.YearMonth> {
+ private static final long serialVersionUID = -8552019996811990307L;
+ public YearMonth() {super(java.time.YearMonth.class);}
+
+ @Override java.time.YearMonth doConvert(String source) throws
DateTimeParseException {
+ return java.time.YearMonth.parse(source);
+ }
+ }
+
+ public static final class MonthDay extends
StringConverter<java.time.MonthDay> {
+ private static final long serialVersionUID = 7647193120429326557L;
+ public MonthDay() {super(java.time.MonthDay.class);}
+
+ @Override java.time.MonthDay doConvert(String source) throws
DateTimeParseException {
+ return java.time.MonthDay.parse(source);
+ }
+ }
+
public static final class Boolean extends
StringConverter<java.lang.Boolean> {
private static final long serialVersionUID = 4689076223535035309L;
public Boolean() {super(java.lang.Boolean.class);}
// Instantiated by ServiceLoader.
diff --git
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/FunctionProperty.java
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/FunctionProperty.java
index 262780a669..f86a62cb1b 100644
---
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/FunctionProperty.java
+++
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/FunctionProperty.java
@@ -80,7 +80,7 @@ public enum FunctionProperty {
/**
* A function is <i>injective</i> if each value of <var>T</var> is either
unrelated
* to <var>S</var>, or is the output of exactly one value of <var>S</var>.
- * For example an {@link org.apache.sis.util.ObjectConverter} doing
conversions from {@link Integer}
+ * For example, an {@link org.apache.sis.util.ObjectConverter} doing
conversions from {@link Integer}
* to {@link String} is an injective function, because no pair of integers
can produce the same string.
*
* <p>A function which is both injective and {@linkplain #SURJECTIVE
surjective} is a
@@ -95,7 +95,7 @@ public enum FunctionProperty {
/**
* A function is <i>surjective</i> if any value of <var>T</var> can be
created
* from one or many values of <var>S</var>.
- * For example an {@link org.apache.sis.util.ObjectConverter} doing
conversions from {@link String}
+ * For example, an {@link org.apache.sis.util.ObjectConverter} doing
conversions from {@link String}
* to {@link Integer} is a surjective function, because there is always at
least one string for each integer value.
* Note that such function cannot be injective since many different
strings can represent the same integer value.
*