This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 4be2f9fa8fdfa04b8a7813cb784e78430edbb22c
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Thu Jan 9 20:28:38 2025 +0100

    Add a `Locale` argument to `MetadataBuilder.parseLegalNotice(…)`.
    Opportunistic code refactoring related to `InternationalString`.
---
 .../org/apache/sis/xml/bind/lan/PT_FreeText.java   |  10 +-
 .../apache/sis/metadata/iso/MarshallingTest.java   |   2 +-
 .../apache/sis/xml/bind/gco/StringAdapterTest.java |   2 +-
 .../sis/xml/bind/lan/FreeTextMarshallingTest.java  |   2 +-
 .../sis/referencing/NamedIdentifierTest.java       |   2 +-
 .../sis/storage/geotiff/ImageFileDirectory.java    |   2 +-
 .../org/apache/sis/storage/base/LegalSymbols.java  | 105 ++++++++++++++++++---
 .../apache/sis/storage/base/MetadataBuilder.java   |   7 +-
 .../sis/storage/base/MetadataBuilderTest.java      |  63 +++++++------
 .../sis/util/DefaultInternationalString.java       |  39 ++++----
 .../sis/util/DefaultInternationalStringTest.java   |   8 +-
 .../sis/util/SimpleInternationalStringTest.java    |   8 +-
 .../sis/util/collection/TreeTableFormatTest.java   |   2 +-
 13 files changed, 170 insertions(+), 82 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/lan/PT_FreeText.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/lan/PT_FreeText.java
index 675a2b8abf..3059253daa 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/lan/PT_FreeText.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/lan/PT_FreeText.java
@@ -103,9 +103,9 @@ public final class PT_FreeText extends GO_CharacterString {
      */
     public static PT_FreeText create(final InternationalString text) {
         if (text instanceof DefaultInternationalString) {
-            final DefaultInternationalString df = (DefaultInternationalString) 
text;
+            final var df = (DefaultInternationalString) text;
             final Set<Locale> locales = df.getLocales();
-            final TextGroup[] textGroup = new TextGroup[locales.size()];
+            final var textGroup = new TextGroup[locales.size()];
             int n = 0;
             for (final Locale locale : locales) {
                 if (locale != null && !locale.equals(Locale.ROOT)) {
@@ -120,8 +120,8 @@ public final class PT_FreeText extends GO_CharacterString {
                  * the implementation (DefaultInternationalString) is known to 
support null.
                  */
                 final Context context = Context.current();
-                return new PT_FreeText(df.toString(context != null ? 
context.getLocale() : null),
-                        ArraysExt.resize(textGroup, n));
+                String s = df.toString(context != null ? context.getLocale() : 
null);
+                return new PT_FreeText(s, ArraysExt.resize(textGroup, n));
             }
         }
         return null;
@@ -137,6 +137,7 @@ public final class PT_FreeText extends GO_CharacterString {
      * @return {@code true} if the given text has been found.
      */
     private boolean contains(final String search) {
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
         final TextGroup[] textGroup = this.textGroup;
         if (textGroup != null) {
             for (final TextGroup group : textGroup) {
@@ -179,6 +180,7 @@ public final class PT_FreeText extends GO_CharacterString {
          * DefaultInternationalString.
          */
         DefaultInternationalString i18n = null;
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
         final TextGroup[] textGroup = this.textGroup;
         if (textGroup != null) {
             for (final TextGroup group : textGroup) {
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/MarshallingTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/MarshallingTest.java
index f9c137845c..61136a694a 100644
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/MarshallingTest.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/MarshallingTest.java
@@ -323,7 +323,7 @@ public final class MarshallingTest extends TestUsingFile 
implements Filter {
          */
         final var dataId = new DefaultDataIdentification();
         {
-            final DefaultInternationalString description = new 
DefaultInternationalString();
+            final var description = new DefaultInternationalString();
             description.add(Locale.ENGLISH, "Metadata for an imaginary map.");
             description.add(Locale.FRENCH,  "Méta-données pour une carte 
imaginaire.");
             dataId.setAbstract(description);
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/gco/StringAdapterTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/gco/StringAdapterTest.java
index 9b1058fe7c..c9f78c8c1d 100644
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/gco/StringAdapterTest.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/gco/StringAdapterTest.java
@@ -55,7 +55,7 @@ public final class StringAdapterTest extends TestCase {
      */
     @Test
     public void testToLocalizedString() {
-        final DefaultInternationalString i18n = new 
DefaultInternationalString();
+        final var i18n = new DefaultInternationalString();
         i18n.add(Locale.ENGLISH,  "A word");
         i18n.add(Locale.FRENCH,   "Un mot");
         i18n.add(Locale.JAPANESE, "言葉");
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/lan/FreeTextMarshallingTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/lan/FreeTextMarshallingTest.java
index 4cc84e31d5..9cdaeb85a3 100644
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/lan/FreeTextMarshallingTest.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/xml/bind/lan/FreeTextMarshallingTest.java
@@ -46,7 +46,7 @@ public final class FreeTextMarshallingTest extends TestCase {
      * Returns the expected string.
      */
     private static DefaultInternationalString getExpectedI18N() {
-        final DefaultInternationalString i18n = new 
DefaultInternationalString();
+        final var i18n = new DefaultInternationalString();
         i18n.add(Locale.ENGLISH, "OpenSource Project");
         i18n.add(Locale.FRENCH,  "Projet OpenSource");
         i18n.add(Locale.ITALIAN, "Progetto OpenSource");
diff --git 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/NamedIdentifierTest.java
 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/NamedIdentifierTest.java
index 93959a31cb..9f3eaea041 100644
--- 
a/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/NamedIdentifierTest.java
+++ 
b/endorsed/src/org.apache.sis.referencing/test/org/apache/sis/referencing/NamedIdentifierTest.java
@@ -105,7 +105,7 @@ public final class NamedIdentifierTest extends TestCase {
      * Creates an internationalized name with a code set to "name" localized 
in English, French and Japanese.
      */
     private NamedIdentifier createI18N() {
-        final DefaultInternationalString i18n = new 
DefaultInternationalString();
+        final var i18n = new DefaultInternationalString();
         i18n.add(Locale.ENGLISH,  "name");
         i18n.add(Locale.FRENCH,   "nom");
         i18n.add(Locale.JAPANESE, "名前");
diff --git 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
index 2249101ef6..bb82527122 100644
--- 
a/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
+++ 
b/endorsed/src/org.apache.sis.storage.geotiff/main/org/apache/sis/storage/geotiff/ImageFileDirectory.java
@@ -1000,7 +1000,7 @@ final class ImageFileDirectory extends DataCube {
              */
             case (short) TAG_COPYRIGHT: {
                 for (final String value : type.readAsStrings(input(), count, 
encoding())) {
-                    metadata.parseLegalNotice(value);
+                    metadata.parseLegalNotice(null, value);
                 }
                 break;
             }
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/LegalSymbols.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/LegalSymbols.java
index 570b35d2e2..457e23abe3 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/LegalSymbols.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/LegalSymbols.java
@@ -16,18 +16,20 @@
  */
 package org.apache.sis.storage.base;
 
-import java.time.LocalDate;
-import java.util.Date;
-import java.util.Collections;
+import java.time.Year;
+import java.util.Locale;
 import org.opengis.metadata.citation.Role;
 import org.opengis.metadata.citation.DateType;
+import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.constraint.Restriction;
+import org.opengis.util.InternationalString;
+import org.apache.sis.util.DefaultInternationalString;
 import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.logging.Logging;
 import org.apache.sis.metadata.iso.citation.AbstractParty;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.metadata.iso.citation.DefaultCitationDate;
 import org.apache.sis.metadata.iso.constraint.DefaultLegalConstraints;
-import static org.apache.sis.util.privy.Constants.MILLISECONDS_PER_DAY;
 
 // Specific to the geoapi-4.0 branch:
 import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
@@ -86,8 +88,12 @@ final class LegalSymbols {
     /**
      * Implementation of {@link MetadataBuilder#parseLegalNotice(String)}, 
provided here for reducing
      * the number of class loading in the common case where there is no legal 
notice to parse.
+     *
+     * @param  locale       the language of the notice, or {@code null} if 
unspecified.
+     * @param  notice       the legal notice.
+     * @param  constraints  where to append the parsed notice.
      */
-    static void parse(final String notice, final DefaultLegalConstraints 
constraints) {
+    static void parse(final Locale locale, final String notice, final 
DefaultLegalConstraints constraints) {
         final int length = notice.length();
         final var buffer = new StringBuilder(length);
         int     year           = 0;         // The copyright year, or 0 if 
none.
@@ -198,6 +204,8 @@ parse:  for (int i = 0; i < length;) {
         }
         /*
          * End of parsing. Omit trailing spaces and some punctuations if any, 
then store the result.
+         * If a `Citation` already exist and could be for the same legal 
notice in different locales,
+         * it will be completed. Otherwise, a new citation will be created.
          */
         int i = buffer.length();
         while (i > 0) {
@@ -205,18 +213,91 @@ parse:  for (int i = 0; i < length;) {
             if (!isSpaceOrPunctuation(c)) break;
             i -= Character.charCount(c);
         }
-        final var c = new DefaultCitation(notice);
+        DefaultCitation citation = null;
+        if (locale != null) {
+            for (Citation c : constraints.getReferences()) {
+                if (c instanceof DefaultCitation) {
+                    if (update(c.getTitle(), locale, notice)) {
+                        citation = (DefaultCitation) c;             // Set 
only on success.
+                        break;
+                    }
+                }
+            }
+        }
+        if (citation == null) {
+            citation = new DefaultCitation(i18n(locale, notice));
+            constraints.getReferences().add(citation);
+        }
         if (year != 0) {
-            final Date date = new Date(LocalDate.of(year, 1, 1).toEpochDay() * 
MILLISECONDS_PER_DAY);
-            c.setDates(Collections.singleton(new DefaultCitationDate(date, 
DateType.IN_FORCE)));
+            final var date = new DefaultCitationDate(Year.of(year), 
DateType.IN_FORCE);
+            final var dates = citation.getDates();
+            if (!dates.contains(date)) {
+                dates.add(date);
+            }
         }
+        /*
+         * At this point, the citation has been created and added to the 
contraints.
+         * If a party already exists, try to update the owner's name. This is 
based
+         * on the assumption that `LegalSymbols` is the only code putting 
'i18n' in
+         * the constraints.
+         */
         if (i != 0) {
             buffer.setLength(i);
+            final String owner = buffer.toString();
+            if (locale != null) {
+                for (final var cited : citation.getCitedResponsibleParties()) {
+                    if (cited.getRole() == Role.OWNER) {
+                        for (final var party : cited.getParties()) {
+                            final var i18n = party.getName();
+                            if (CharSequences.startsWith(owner, 
i18n.toString(Locale.ENGLISH), true)) {
+                                /*
+                                 * Use case: name is followed by unwanted text 
because the
+                                 * `VALUES` special cases are provided in 
English only.
+                                 * Example: "John Smith, Tous droits réservés."
+                                 */
+                                return;
+                            }
+                            if (update(i18n, locale, owner)) {
+                                return;
+                            }
+                        }
+                    }
+                }
+            }
             // Same limitation as MetadataBuilder.party().
-            final var party = new AbstractParty(buffer, null);
-            final var r = new DefaultResponsibility(Role.OWNER, null, party);
-            c.setCitedResponsibleParties(Collections.singleton(r));
+            var party = new AbstractParty(i18n(locale, owner), null);
+            var cited = new DefaultResponsibility(Role.OWNER, null, party);
+            citation.getCitedResponsibleParties().add(cited);
         }
-        constraints.getReferences().add(c);
+    }
+
+    /**
+     * If the given international string is an instance of {@link 
DefaultInternationalString},
+     * add the language to it and returns {@code true}. Otherwise, returns 
{@code false} for
+     * notifying the caller that it needs to update the metadata itself.
+     *
+     * @param  i18n    the international string to update if possible.
+     * @param  locale  locale of the text.
+     * @param  text    the localized text.
+     */
+    private static boolean update(final InternationalString i18n, final Locale 
locale, final String text) {
+        if (i18n instanceof DefaultInternationalString) try {
+            ((DefaultInternationalString) i18n).add(locale, text);
+            return true;
+        } catch (IllegalArgumentException e) {
+            Logging.ignorableException(StoreUtilities.LOGGER, 
MetadataBuilder.class, "parseLegalNotice", e);
+        }
+        return (i18n != null) && text.equalsIgnoreCase(i18n.toString());
+    }
+
+    /**
+     * Creates a potentially international string for the given text.
+     *
+     * @param  locale  locale of the text, or {@code null} if unspecified.
+     * @param  text    the text to internationalize.
+     * @return the potentially international text.
+     */
+    private static CharSequence i18n(final Locale locale, final String text) {
+        return (locale != null) ? new DefaultInternationalString(locale, text) 
: text;
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
index 21b5586427..b7ea27fe0a 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/base/MetadataBuilder.java
@@ -1620,7 +1620,7 @@ public class MetadataBuilder {
      *             └─Reference
      *                 ├─Title……………………………………………… Copyright (C), John Smith, 
1992. All rights reserved.
      *                 ├─Date
-     *                 │   ├─Date……………………………………… 1992-01-01
+     *                 │   ├─Date……………………………………… 1992
      *                 │   └─Date type………………………… In force
      *                 └─Cited responsible party
      *                     ├─Party
@@ -1633,11 +1633,12 @@ public class MetadataBuilder {
      *   <li>{@code metadata/identificationInfo/resourceConstraint}</li>
      * </ul>
      *
+     * @param  locale  the language of the notice, or {@code null} if 
unspecified.
      * @param  notice  the legal notice, or {@code null} for no-operation.
      */
-    public final void parseLegalNotice(final String notice) {
+    public final void parseLegalNotice(final Locale locale, final String 
notice) {
         if (notice != null) {
-            LegalSymbols.parse(notice, constraints());
+            LegalSymbols.parse(locale, notice, constraints());
         }
     }
 
diff --git 
a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/base/MetadataBuilderTest.java
 
b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/base/MetadataBuilderTest.java
index a3d8f44178..ec9e903f8a 100644
--- 
a/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/base/MetadataBuilderTest.java
+++ 
b/endorsed/src/org.apache.sis.storage/test/org/apache/sis/storage/base/MetadataBuilderTest.java
@@ -16,7 +16,9 @@
  */
 package org.apache.sis.storage.base;
 
+import java.time.Year;
 import java.util.Map;
+import java.util.Locale;
 import org.opengis.util.GenericName;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.content.ContentInformation;
@@ -30,7 +32,6 @@ import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 import static org.apache.sis.metadata.Assertions.assertTitleEquals;
 import static org.apache.sis.metadata.Assertions.assertPartyNameEquals;
-import static org.apache.sis.test.TestUtilities.date;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -87,51 +88,50 @@ public final class MetadataBuilderTest extends TestCase {
      */
     private static void verifyCopyrightParsing(final String notice) {
         final var builder = new MetadataBuilder();
-        builder.parseLegalNotice(notice);
-        final var constraints = assertInstanceOf(LegalConstraints.class,
-                
getSingleton(getSingleton(builder.build().getIdentificationInfo()).getResourceConstraints()));
-
-        assertEquals(Restriction.COPYRIGHT, 
getSingleton(constraints.getUseConstraints()));
-        final Citation ref = getSingleton(constraints.getReferences());
+        builder.parseLegalNotice(null, notice);
+        final Citation ref = copyright(builder);
         assertTitleEquals(notice, ref, "reference.title");
         assertPartyNameEquals("John Smith", ref, 
"reference.citedResponsibleParty");
-        assertEquals(date("1992-01-01 00:00:00"), 
getSingleton(ref.getDates()).getDate());
+        assertEquals(Year.of(1992), 
getSingleton(ref.getDates()).getReferenceDate());
     }
 
     /**
-     * Tests {@link MetadataBuilder#addFeatureType(FeatureType, long)}.
-     *
-     * @todo Combine the 4 tests in a single one for leveraging the same 
{@link DefaultFeatureType} instance?
-     *       It would be consistent with {@link #testParseLegalNotice()}, and 
the error message in those tests
-     *       are already quite clear.
+     * Returns the citation of the legal constraints built by the given 
builder.
+     * This method verifies that the constraint is a copyright.
      */
-    @Test
-    public void negative_feature_count_are_ignored() {
-        verifyFeatureInstanceCount("Feature count should not be written if it 
is negative", null, -1);
-    }
-
-    /**
-     * Tests {@link MetadataBuilder#addFeatureType(FeatureType, long)}.
-     */
-    @Test
-    public void no_overflow_on_feature_count() {
-        verifyFeatureInstanceCount("Feature count should be limited to maximum 
32bit integer value", Integer.MAX_VALUE, 7_000_000_000L);
+    private static Citation copyright(final MetadataBuilder builder) {
+        final var id = getSingleton(builder.build().getIdentificationInfo());
+        final var constraints = assertInstanceOf(LegalConstraints.class, 
getSingleton(id.getResourceConstraints()));
+        assertEquals(Restriction.COPYRIGHT, 
getSingleton(constraints.getUseConstraints()));
+        return getSingleton(constraints.getReferences());
     }
 
     /**
-     * Tests {@link MetadataBuilder#addFeatureType(FeatureType, long)}.
+     * Tests {@link MetadataBuilder#parseLegalNotice(String)} with different 
languages.
      */
     @Test
-    public void verify_feature_count_is_written() {
-        verifyFeatureInstanceCount("Feature count should be written as is", 
42, 42);
+    public void testParseLegalNoticeLocalized() {
+        final var builder = new MetadataBuilder();
+        builder.parseLegalNotice(Locale.ENGLISH, "Copyright (C), John Smith, 
1997. All rights reserved.");
+        builder.parseLegalNotice(Locale.FRENCH,  "Copyright (C), John Smith, 
1997. Tous droits réservés.");
+        final Citation ref = copyright(builder);
+        assertEquals(Year.of(1997), 
getSingleton(ref.getDates()).getReferenceDate());
+        assertPartyNameEquals("John Smith", ref, 
"reference.citedResponsibleParty");
+        final var title = ref.getTitle();
+        assertEquals("Copyright (C), John Smith, 1997. All rights reserved.",  
title.toString(Locale.ENGLISH));
+        assertEquals("Copyright (C), John Smith, 1997. Tous droits réservés.", 
title.toString(Locale.FRENCH));
     }
 
     /**
      * Tests {@link MetadataBuilder#addFeatureType(FeatureType, long)}.
      */
     @Test
-    public void feature_should_be_ignored_when_count_is_zero() {
-        verifyFeatureInstanceCount("Feature should not be written if count is 
0", null, 0);
+    public void testAddFeatureType() {
+        final var dataType = new 
DefaultFeatureType(Map.of(DefaultFeatureType.NAME_KEY, "Test type"), false, 
null);
+        verifyFeatureInstanceCount(dataType, "Feature count should not be 
written if it is negative", null, -1);
+        verifyFeatureInstanceCount(dataType, "Feature count should be limited 
to maximum 32bit integer value", Integer.MAX_VALUE, 7_000_000_000L);
+        verifyFeatureInstanceCount(dataType, "Feature count should be written 
as is", 42, 42);
+        verifyFeatureInstanceCount(dataType, "Feature should not be written if 
count is 0", null, 0);
     }
 
     /**
@@ -142,8 +142,9 @@ public final class MetadataBuilderTest extends TestCase {
      * @param expected       the feature instance count value we want to see 
in the metadata (control value).
      * @param valueToInsert  the value to send to the metadata builder.
      */
-    private static void verifyFeatureInstanceCount(final String errorMessage, 
final Integer expected, final long valueToInsert) {
-        final var dataType = new 
DefaultFeatureType(Map.of(DefaultFeatureType.NAME_KEY, "Test type"), false, 
null);
+    private static void verifyFeatureInstanceCount(final DefaultFeatureType 
dataType,
+            final String errorMessage, final Integer expected, final long 
valueToInsert)
+    {
         final var builder  = new MetadataBuilder();
         final GenericName name = builder.addFeatureType(dataType, 
valueToInsert);
         assertNotNull(name);
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/DefaultInternationalString.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/DefaultInternationalString.java
index e71dea3b1a..94e544d5ba 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/DefaultInternationalString.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/DefaultInternationalString.java
@@ -63,12 +63,6 @@ public class DefaultInternationalString extends 
AbstractInternationalString impl
     @SuppressWarnings("serial")
     private Map<Locale,String> localeMap;
 
-    /**
-     * An unmodifiable view of the entry set in {@link #localeMap}. This is 
the set of locales
-     * defined in this international string. Will be constructed only when 
first requested.
-     */
-    private transient Set<Locale> localeSet;
-
     /**
      * Creates an initially empty international string. Localized strings can 
be added
      * using one of {@link #add add(…)} methods.
@@ -93,6 +87,21 @@ public class DefaultInternationalString extends 
AbstractInternationalString impl
         }
     }
 
+    /**
+     * Creates an international string initialized with the given string in 
the given locale.
+     * Additional localized strings can be added using one of {@link #add 
add(…)} methods.
+     *
+     * @param  locale  the locale for the {@code string} value.
+     * @param  string  the localized string.
+     *
+     * @since 1.5
+     */
+    public DefaultInternationalString(final Locale locale, String string) {
+        ArgumentChecks.ensureNonNull("locale", locale);
+        ArgumentChecks.ensureNonNull("string", string);
+        localeMap = Collections.singletonMap(locale, string);
+    }
+
     /**
      * Creates an international string initialized with the given localized 
strings.
      * The content of the given map is copied, so changes to that map after 
construction
@@ -135,14 +144,12 @@ public class DefaultInternationalString extends 
AbstractInternationalString impl
         switch (localeMap.size()) {
             case 0: {
                 localeMap = Collections.singletonMap(locale, string);
-                localeSet = null;
                 defaultValue = null;                                // Will be 
recomputed when first needed.
                 return;
             }
             case 1: {
                 // If HashMap is replaced by another type, revisit 
`getLocales()`.
                 localeMap = new LinkedHashMap<>(localeMap);
-                localeSet = null;
                 break;
             }
         }
@@ -167,13 +174,9 @@ public class DefaultInternationalString extends 
AbstractInternationalString impl
      *       on the same lock as the one used for accessing the internal 
locale map.
      */
     public synchronized Set<Locale> getLocales() {
-        Set<Locale> locales = localeSet;
-        if (locales == null) {
-            locales = localeMap.keySet();
-            if (localeMap instanceof HashMap<?,?>) {
-                locales = Collections.unmodifiableSet(locales);
-            }
-            localeSet = locales;
+        Set<Locale> locales = localeMap.keySet();
+        if (localeMap instanceof HashMap<?,?>) {
+            locales = Collections.unmodifiableSet(locales);
         }
         return locales;
     }
@@ -319,7 +322,7 @@ public class DefaultInternationalString extends 
AbstractInternationalString impl
      */
     public synchronized boolean isSubsetOf(final Object candidate) {
         if (candidate instanceof InternationalString) {
-            final InternationalString string = (InternationalString) candidate;
+            final var string = (InternationalString) candidate;
             for (final Map.Entry<Locale,String> entry : localeMap.entrySet()) {
                 final Locale locale = entry.getKey();
                 final String text   = entry.getValue();
@@ -335,7 +338,7 @@ public class DefaultInternationalString extends 
AbstractInternationalString impl
                 }
             }
         } else if (candidate instanceof Map<?,?>) {
-            final Map<?,?> map = (Map<?,?>) candidate;
+            final var map = (Map<?,?>) candidate;
             return map.entrySet().containsAll(localeMap.entrySet());
         } else {
             return false;
@@ -352,7 +355,7 @@ public class DefaultInternationalString extends 
AbstractInternationalString impl
     @Override
     public boolean equals(final Object object) {
         if (object != null && object.getClass() == getClass()) {
-            final DefaultInternationalString that = 
(DefaultInternationalString) object;
+            final var that = (DefaultInternationalString) object;
             return Objects.equals(this.localeMap, that.localeMap);
         }
         return false;
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/DefaultInternationalStringTest.java
 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/DefaultInternationalStringTest.java
index fdee7b7772..e824598fe2 100644
--- 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/DefaultInternationalStringTest.java
+++ 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/DefaultInternationalStringTest.java
@@ -49,7 +49,7 @@ public final class DefaultInternationalStringTest extends 
TestCase {
      */
     @Test
     public void testEnglishOnly() {
-        final DefaultInternationalString toTest = new 
DefaultInternationalString();
+        final var toTest = new DefaultInternationalString();
         toTest.add(Locale.ENGLISH, MESSAGE);
         assertSame(MESSAGE, toTest.toString());
         assertSame(MESSAGE, toTest.toString(null));
@@ -62,7 +62,7 @@ public final class DefaultInternationalStringTest extends 
TestCase {
      */
     @Test
     public void testEnglishAndFrench() {
-        final DefaultInternationalString toTest = new 
DefaultInternationalString(MESSAGE);
+        final var toTest = new DefaultInternationalString(MESSAGE);
         assertSame(MESSAGE, toTest.toString());
         toTest.add(Locale.ENGLISH,       MESSAGE_en);
         toTest.add(Locale.FRENCH,        MESSAGE_fr);     
assertLocalized(toTest, MESSAGE_fr);
@@ -75,7 +75,7 @@ public final class DefaultInternationalStringTest extends 
TestCase {
      */
     @Test
     public void testSerialization() {
-        final DefaultInternationalString toTest = new 
DefaultInternationalString(MESSAGE);
+        final var toTest = new DefaultInternationalString(MESSAGE);
         toTest.add(Locale.ENGLISH,       MESSAGE_en);
         toTest.add(Locale.FRENCH,        MESSAGE_fr);
         toTest.add(Locale.CANADA_FRENCH, MESSAGE_fr_CA);
@@ -102,7 +102,7 @@ public final class DefaultInternationalStringTest extends 
TestCase {
      */
     @Test
     public void testFormattable() {
-        final DefaultInternationalString toTest = new 
DefaultInternationalString(MESSAGE);
+        final var toTest = new DefaultInternationalString(MESSAGE);
         toTest.add(Locale.ENGLISH,       MESSAGE_en);
         toTest.add(Locale.FRENCH,        MESSAGE_fr);
         toTest.add(Locale.CANADA_FRENCH, MESSAGE_fr_CA);
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/SimpleInternationalStringTest.java
 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/SimpleInternationalStringTest.java
index 8cf6c18f1a..0ddd3503bf 100644
--- 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/SimpleInternationalStringTest.java
+++ 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/SimpleInternationalStringTest.java
@@ -44,7 +44,7 @@ public final class SimpleInternationalStringTest extends 
TestCase {
      */
     @Test
     public void testSimple() {
-        final SimpleInternationalString toTest = new 
SimpleInternationalString(MESSAGE);
+        final var toTest = new SimpleInternationalString(MESSAGE);
         assertSame(MESSAGE, toTest.toString());
         assertSame(MESSAGE, toTest.toString(null));
         assertSame(MESSAGE, toTest.toString(Locale.ROOT));
@@ -57,8 +57,8 @@ public final class SimpleInternationalStringTest extends 
TestCase {
      */
     @Test
     public void testSerialization() {
-        final SimpleInternationalString before = new 
SimpleInternationalString(MESSAGE);
-        final SimpleInternationalString after  = 
assertSerializedEquals(before);
+        final var before = new SimpleInternationalString(MESSAGE);
+        final var after  = assertSerializedEquals(before);
         assertEquals(MESSAGE, after.toString());
         assertEquals(MESSAGE, after.toString(null));
         assertEquals(MESSAGE, after.toString(Locale.ROOT));
@@ -71,7 +71,7 @@ public final class SimpleInternationalStringTest extends 
TestCase {
      */
     @Test
     public void testPrintf() {
-        final SimpleInternationalString toTest = new 
SimpleInternationalString(MESSAGE);
+        final var toTest = new SimpleInternationalString(MESSAGE);
         assertEquals(MESSAGE,                               
String.format("%s", toTest));
         assertEquals("    This is an unlocalized message.", 
String.format("%35s", toTest));
         assertEquals("This is an unlocalized message.    ", 
String.format("%-35s", toTest));
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/TreeTableFormatTest.java
 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/TreeTableFormatTest.java
index 040f3f63a7..5f740a73de 100644
--- 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/TreeTableFormatTest.java
+++ 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/TreeTableFormatTest.java
@@ -220,7 +220,7 @@ public final class TreeTableFormatTest extends TestCase {
      * do not have translations in all tested languages.
      */
     private static void testLocalizedFormatInEnglishEnvironment() {
-        final DefaultInternationalString i18n = new 
DefaultInternationalString();
+        final var i18n = new DefaultInternationalString();
         i18n.add(Locale.ENGLISH,  "An English sentence");
         i18n.add(Locale.FRENCH,   "Une phrase en français");
         i18n.add(Locale.JAPANESE, "日本語の言葉");

Reply via email to