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 dca5af8fc9 Update for a change in the way that CodeList are 
constructed in GeoAPI. https://github.com/opengeospatial/geoapi/issues/91
dca5af8fc9 is described below

commit dca5af8fc9a814c96f65c11b408c66616ee7c325
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Tue Mar 12 17:24:36 2024 +0100

    Update for a change in the way that CodeList are constructed in GeoAPI.
    https://github.com/opengeospatial/geoapi/issues/91
---
 .../apache/sis/metadata/iso/legacy/MediumName.java |  77 ++++++---------
 .../apache/sis/metadata/sql/MetadataFallback.java  |   9 +-
 .../apache/sis/metadata/sql/MetadataSource.java    |  14 ++-
 .../main/org/apache/sis/util/iso/Types.java        |  78 +++++++++++++--
 .../apache/sis/xml/bind/cat/CodeListAdapter.java   |   9 +-
 .../sis/xml/bind/gco/GO_CharacterString.java       |  24 +++--
 .../apache/sis/xml/bind/gml/CodeListAdapter.java   |  11 ++-
 .../apache/sis/xml/bind/gml/SC_VerticalCRS.java    |   2 +-
 .../sis/xml/bind/metadata/replace/SensorType.java  |  49 ++--------
 .../apache/sis/metadata/TreeNodeChildrenTest.java  |  18 ++--
 .../metadata/iso/citation/DefaultContactTest.java  |   2 +-
 .../test/org/apache/sis/util/iso/TypesTest.java    |  22 ++---
 .../apache/sis/io/wkt/GeodeticObjectParser.java    |   4 +-
 .../sis/referencing/cs/DirectionAlongMeridian.java |   2 +-
 .../referencing/internal/VerticalDatumTypes.java   |   7 +-
 .../apache/sis/storage/netcdf/MetadataReader.java  |  12 ++-
 .../org/apache/sis/storage/netcdf/base/Axis.java   |   4 +-
 .../org/apache/sis/converter/StringConverter.java  |  10 +-
 .../apache/sis/util/collection/CodeListSet.java    |   5 +-
 .../main/org/apache/sis/util/privy/CodeLists.java  | 106 ++++++++++++---------
 .../sis/util/collection/CodeListSetTest.java       |   2 +-
 .../apache/sis/util/collection/LargeCodeList.java  |  31 +++---
 geoapi/snapshot                                    |   2 +-
 .../gui/referencing/PositionableProjection.java    |  48 +++-------
 .../apache/sis/gui/referencing/package-info.java   |   2 +-
 25 files changed, 290 insertions(+), 260 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/legacy/MediumName.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/legacy/MediumName.java
index 14d6ad1afd..6901065c3c 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/legacy/MediumName.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/iso/legacy/MediumName.java
@@ -16,15 +16,12 @@
  */
 package org.apache.sis.metadata.iso.legacy;
 
-import java.util.List;
-import java.util.ArrayList;
 import org.opengis.annotation.UML;
 import org.opengis.metadata.citation.Citation;
 import org.opengis.util.CodeList;
 import org.opengis.util.InternationalString;
 import static org.opengis.annotation.Obligation.*;
 import static org.opengis.annotation.Specification.*;
-import org.apache.sis.util.privy.CodeLists;
 import org.apache.sis.util.iso.Types;
 
 
@@ -42,84 +39,86 @@ public final class MediumName extends CodeList<MediumName> 
implements Citation {
     /** Serial number for compatibility with different versions. */
     private static final long serialVersionUID = 7157038832444373933L;
 
-    /** List of all enumerations of this type. */
-    private static final List<MediumName> VALUES = new ArrayList<>(18);
+    /*
+     * We need to construct values with `valueOf(String)` instead of the 
constructor
+     * because this package is not exported to GeoAPI. See `CodeList` class 
javadoc.
+     */
 
     /** Read-only optical disk. */
     @UML(identifier="cdRom", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName CD_ROM = new MediumName("CD_ROM");
+    public static final MediumName CD_ROM = valueOf("CD_ROM");
 
     /** Digital versatile disk. */
     @UML(identifier="dvd", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName DVD = new MediumName("DVD");
+    public static final MediumName DVD = valueOf("DVD");
 
     /** Digital versatile disk digital versatile disk, read only. */
     @UML(identifier="dvdRom", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName DVD_ROM = new MediumName("DVD_ROM");
+    public static final MediumName DVD_ROM = valueOf("DVD_ROM");
 
     /** 3½ inch magnetic disk. */
     @UML(identifier="3halfInchFloppy", obligation=CONDITIONAL, 
specification=ISO_19115)
-    public static final MediumName FLOPPY_3_HALF_INCH = new 
MediumName("FLOPPY_3_HALF_INCH");
+    public static final MediumName FLOPPY_3_HALF_INCH = 
valueOf("FLOPPY_3_HALF_INCH");
 
     /** 5¼ inch magnetic disk. */
     @UML(identifier="5quarterInchFloppy", obligation=CONDITIONAL, 
specification=ISO_19115)
-    public static final MediumName FLOPPY_5_QUARTER_INCH = new 
MediumName("FLOPPY_5_QUARTER_INCH");
+    public static final MediumName FLOPPY_5_QUARTER_INCH = 
valueOf("FLOPPY_5_QUARTER_INCH");
 
     /** 7 track magnetic tape. */
     @UML(identifier="7trackTape", obligation=CONDITIONAL, 
specification=ISO_19115)
-    public static final MediumName TAPE_7_TRACK = new 
MediumName("TAPE_7_TRACK");
+    public static final MediumName TAPE_7_TRACK = valueOf("TAPE_7_TRACK");
 
     /** 9 track magnetic tape. */
     @UML(identifier="9trackTape", obligation=CONDITIONAL, 
specification=ISO_19115)
-    public static final MediumName TAPE_9_TRACK = new 
MediumName("TAPE_9_TRACK");
+    public static final MediumName TAPE_9_TRACK = valueOf("TAPE_9_TRACK");
 
     /** 3480 cartridge tape drive. */
     @UML(identifier="3480Cartridge", obligation=CONDITIONAL, 
specification=ISO_19115)
-    public static final MediumName CARTRIDGE_3480 = new 
MediumName("CARTRIDGE_3480");
+    public static final MediumName CARTRIDGE_3480 = valueOf("CARTRIDGE_3480");
 
     /** 3490 cartridge tape drive. */
     @UML(identifier="3490Cartridge", obligation=CONDITIONAL, 
specification=ISO_19115)
-    public static final MediumName CARTRIDGE_3490 = new 
MediumName("CARTRIDGE_3490");
+    public static final MediumName CARTRIDGE_3490 = valueOf("CARTRIDGE_3490");
 
     /** 3580 cartridge tape drive. */
     @UML(identifier="3580Cartridge", obligation=CONDITIONAL, 
specification=ISO_19115)
-    public static final MediumName CARTRIDGE_3580 = new 
MediumName("CARTRIDGE_3580");
+    public static final MediumName CARTRIDGE_3580 = valueOf("CARTRIDGE_3580");
 
     /** 4 millimetre magnetic tape. */
     @UML(identifier="4mmCartridgeTape", obligation=CONDITIONAL, 
specification=ISO_19115)
-    public static final MediumName CARTRIDGE_TAPE_4mm = new 
MediumName("CARTRIDGE_TAPE_4mm");
+    public static final MediumName CARTRIDGE_TAPE_4mm = 
valueOf("CARTRIDGE_TAPE_4mm");
 
     /** 8 millimetre magnetic tape. */
     @UML(identifier="8mmCartridgeTape", obligation=CONDITIONAL, 
specification=ISO_19115)
-    public static final MediumName CARTRIDGE_TAPE_8mm = new 
MediumName("CARTRIDGE_TAPE_8mm");
+    public static final MediumName CARTRIDGE_TAPE_8mm = 
valueOf("CARTRIDGE_TAPE_8mm");
 
     /** ¼ inch magnetic tape. */
     @UML(identifier="1quarterInchCartridgeTape", obligation=CONDITIONAL, 
specification=ISO_19115)
-    public static final MediumName CARTRIDGE_TAPE_1_QUARTER_INCH = new 
MediumName("CARTRIDGE_TAPE_1_QUARTER_INCH");
+    public static final MediumName CARTRIDGE_TAPE_1_QUARTER_INCH = 
valueOf("CARTRIDGE_TAPE_1_QUARTER_INCH");
 
     /** Half inch cartridge streaming tape drive. */
     @UML(identifier="digitalLinearTape", obligation=CONDITIONAL, 
specification=ISO_19115)
-    public static final MediumName DIGITAL_LINEAR_TAPE = new 
MediumName("DIGITAL_LINEAR_TAPE");
+    public static final MediumName DIGITAL_LINEAR_TAPE = 
valueOf("DIGITAL_LINEAR_TAPE");
 
     /** Direct computer linkage. */
     @UML(identifier="onLine", obligation=CONDITIONAL, specification=ISO_19115)
-    public static final MediumName ON_LINE = new MediumName("ON_LINE");
+    public static final MediumName ON_LINE = valueOf("ON_LINE");
 
     /** Linkage through a satellite communication system. */
     @UML(identifier="satellite", obligation=CONDITIONAL, 
specification=ISO_19115)
-    public static final MediumName SATELLITE = new MediumName("SATELLITE");
+    public static final MediumName SATELLITE = valueOf("SATELLITE");
 
     /** Communication through a telephone network. */
     @UML(identifier="telephoneLink", obligation=CONDITIONAL, 
specification=ISO_19115)
-    public static final MediumName TELEPHONE_LINK = new 
MediumName("TELEPHONE_LINK");
+    public static final MediumName TELEPHONE_LINK = valueOf("TELEPHONE_LINK");
 
     /** Pamphlet or leaflet giving descriptive information. */
     @UML(identifier="hardcopy", obligation=CONDITIONAL, 
specification=ISO_19115)
-    public static final MediumName HARDCOPY = new MediumName("HARDCOPY");
+    public static final MediumName HARDCOPY = valueOf("HARDCOPY");
 
     /** Constructs an element of the given name. */
     private MediumName(final String name) {
-        super(name, VALUES);
+        super(name);
     }
 
     /**
@@ -127,41 +126,19 @@ public final class MediumName extends 
CodeList<MediumName> implements Citation {
      *
      * @return the list of codes declared in the current JVM.
      */
-    public static MediumName[] values() {
-        synchronized (VALUES) {
-            return VALUES.toArray(MediumName[]::new);
-        }
-    }
-
-    /**
-     * Returns the list of codes of the same kind as this code list element.
-     *
-     * @return all code {@linkplain #values() values} for this code list.
-     */
     @Override
     public MediumName[] family() {
-        return values();
-    }
-
-    /**
-     * Disables the search for UML identifiers because we do not export this 
package to GeoAPI.
-     *
-     * @return {@code null}.
-     */
-    @Override
-    public String identifier() {
-        return null;
+        return values(MediumName.class);
     }
 
     /**
-     * Returns the medium name that matches the given string, or {@code null} 
if none match it.
-     * Contrarily to non-deprecated code list, this method does not create a 
new code if none match the given name.
+     * Returns the medium name that matches the given string, or a new one if 
none match it.
      *
      * @param  code  the name of the code to fetch or to create.
      * @return a code matching the given name, or {@code null}.
      */
     public static MediumName valueOf(final String code) {
-        return CodeLists.forName(MediumName.class, code, false);
+        return valueOf(MediumName.class, code, MediumName::new).get();
     }
 
     /**
@@ -177,7 +154,7 @@ public final class MediumName extends CodeList<MediumName> 
implements Citation {
         if (citation != null) {
             final InternationalString title = citation.getTitle();
             if (title != null) {
-                return valueOf(title.toString());
+                return Types.forCodeName(MediumName.class, title.toString(), 
null);
             }
         }
         return null;
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataFallback.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataFallback.java
index b512642248..5ce03ca4ea 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataFallback.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataFallback.java
@@ -25,6 +25,7 @@ import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.metadata.iso.citation.DefaultOrganisation;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.privy.Constants;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.xml.NilReason;
 
@@ -75,12 +76,16 @@ final class MetadataFallback extends MetadataSource {
      * @return an implementation of the required interface, or the code list 
element.
      */
     @Override
-    public <T> T lookup(final Class<T> type, final String identifier) {
+    public <T> T lookup(final Class<T> type, final String identifier) throws 
MetadataStoreException {
         ArgumentChecks.ensureNonNull("type", type);
         ArgumentChecks.ensureNonEmpty("identifier", identifier);
         Object value;
         if (ControlledVocabulary.class.isAssignableFrom(type)) {
-            value = getCodeList(type, identifier);
+            try {
+                value = getCodeList(type, identifier);
+            } catch (IllegalArgumentException e) {
+                throw new 
MetadataStoreException(Errors.format(Errors.Keys.DatabaseError_2, type, 
identifier), e);
+            }
         } else {
             value = null;
             if (type == Citation.class) {
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
index 01e8449b99..49ad8d16ea 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java
@@ -67,6 +67,7 @@ import org.apache.sis.util.UnconvertibleObjectException;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.privy.Strings;
+import org.apache.sis.util.privy.CodeLists;
 import org.apache.sis.util.privy.CollectionsExt;
 import org.apache.sis.util.privy.UnmodifiableArrayList;
 import org.apache.sis.util.collection.Containers;
@@ -878,7 +879,11 @@ public class MetadataSource implements AutoCloseable {
     private Object lookup(final Class<?> type, final String identifier, 
boolean verify) throws MetadataStoreException {
         Object value;
         if (ControlledVocabulary.class.isAssignableFrom(type)) {
-            value = getCodeList(type, identifier);
+            try {
+                value = getCodeList(type, identifier);
+            } catch (IllegalArgumentException e) {
+                throw new 
MetadataStoreException(Errors.format(Errors.Keys.DatabaseError_2, type, 
identifier), e);
+            }
         } else {
             final CacheKey key = new CacheKey(type, identifier);
             /*
@@ -1060,13 +1065,16 @@ public class MetadataSource implements AutoCloseable {
     /**
      * Returns the code of the given type and name. This method is defined for 
avoiding the compiler warning
      * message when the actual class is unknown (it must have been checked 
dynamically by the caller however).
+     *
+     * @return the requested code, or {@code null} if the given name is null 
or empty.
+     * @throws IllegalArgumentException if there is no value for the given 
name and the code cannot be created.
      */
     @SuppressWarnings("unchecked")
     static ControlledVocabulary getCodeList(final Class<?> type, final String 
name) {
         if (type.isEnum()) {
-            return (ControlledVocabulary) 
Types.forEnumName(type.asSubclass(Enum.class), name);
+            return (ControlledVocabulary) 
CodeLists.forEnumName(type.asSubclass(Enum.class), name);
         } else {
-            return Types.forCodeName(type.asSubclass(CodeList.class), name, 
true);
+            return CodeLists.getOrCreate(type.asSubclass(CodeList.class), 
name);
         }
     }
 
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/Types.java 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/Types.java
index b175797473..ace814308b 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/Types.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/Types.java
@@ -26,7 +26,9 @@ import java.util.IllformedLocaleException;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.logging.LogRecord;
+import java.util.function.Function;
 import java.io.IOException;
+import java.lang.reflect.Array;
 import org.opengis.annotation.UML;
 import org.opengis.util.CodeList;
 import org.opengis.util.InternationalString;
@@ -43,6 +45,7 @@ import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Messages;
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.privy.CodeLists;
+import org.apache.sis.util.privy.Strings;
 import org.apache.sis.pending.jdk.JDK19;
 import org.apache.sis.system.Modules;
 
@@ -98,7 +101,7 @@ import org.opengis.util.ControlledVocabulary;
  *   }
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   0.3
  */
 public final class Types extends Static {
@@ -471,9 +474,21 @@ public final class Types extends Static {
      * @return the list of values for the given code list or enumeration, or 
an empty array if none.
      *
      * @see Class#getEnumConstants()
+     *
+     * @deprecated This method depends on reflection, which is restricted in 
the context of Java Module System.
+     *             Instead, {@code T.values()} static methods should be 
invoked directly as much as possible.
      */
+    @SuppressWarnings("unchecked")
+    @Deprecated(since="1.5", forRemoval=true)
     public static <T extends ControlledVocabulary> T[] getCodeValues(final 
Class<T> codeType) {
-        return CodeLists.values(codeType);
+        if (CodeList.class.isAssignableFrom(codeType)) {
+            return (T[]) CodeList.values((Class) codeType);
+        }
+        final T[] codes = codeType.getEnumConstants();
+        if (codes != null) {
+            return codes;
+        }
+        return (T[]) Array.newInstance(codeType, 0);
     }
 
     /**
@@ -579,8 +594,13 @@ public final class Types extends Static {
      *
      * @since 0.5
      */
+    @OptionalCandidate
     public static <T extends Enum<T>> T forEnumName(final Class<T> enumType, 
String name) {
-        return CodeLists.forName(enumType, name);
+        try {
+            return CodeLists.forEnumName(enumType, name);
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
     }
 
     /**
@@ -594,6 +614,47 @@ public final class Types extends Static {
      *       Spaces and punctuation characters like {@code '_'} and {@code 
'-'} are ignored.</li>
      * </ul>
      *
+     * If no match is found, then a new code is created only if the {@code 
creator} argument is non-null.
+     * That argument should be a lambda function to the {@code 
valueOf(String)} method of the code list class.
+     * Example:
+     *
+     * {@snippet lang="java" :
+     *   AxisDirection dir = Types.forCodeName(AxisDirection.class, name, 
AxisDirection::valueOf);
+     *   }
+     *
+     * If the {@code constructor} is null and no existing code matches the 
given name,
+     * then this method returns {@code null}.
+     *
+     * @param  <T>          the compile-time type given as the {@code 
codeType} parameter.
+     * @param  codeType     the type of code list.
+     * @param  name         the name of the code to obtain, or {@code null}.
+     * @param  constructor  the constructor to use if a new code needs to be 
created,
+     *                      or {@code null} for not creating any new code.
+     * @return a code matching the given name, or {@code null} if the name is 
null
+     *         or if no matching code is found and {@code constructor} is 
{@code null}.
+     *
+     * @see #getCodeName(ControlledVocabulary)
+     * @see CodeList#valueOf(Class, String, Function)
+     *
+     * @since 1.5
+     */
+    @OptionalCandidate
+    public static <T extends CodeList<T>> T forCodeName(final Class<T> 
codeType, String name,
+            final Function<? super String, ? extends T> constructor)
+    {
+        name = Strings.trimOrNull(name);
+        if (name == null) {
+            return null;        // Avoid initialization of the <T> class.
+        }
+        T code = CodeLists.forCodeName(codeType, name);
+        if (code == null && constructor != null) {
+            code = constructor.apply(name);
+        }
+        return code;
+    }
+
+    /**
+     * Returns the code of the given type that matches the given name, or 
optionally returns a new one.
      * If no match is found, then a new code is created only if the {@code 
canCreate} argument is {@code true}.
      * Otherwise this method returns {@code null}.
      *
@@ -604,11 +665,16 @@ public final class Types extends Static {
      * @return a code matching the given name, or {@code null} if the name is 
null
      *         or if no matching code is found and {@code canCreate} is {@code 
false}.
      *
-     * @see #getCodeName(ControlledVocabulary)
-     * @see CodeList#valueOf(Class, String)
+     * @deprecated This method depends on reflection, which is restricted in 
the context of Java Module System.
+     *             Replaced by {@link #forCodeName(Class, String, Function)}.
      */
+    @Deprecated(since="1.5", forRemoval=true)
     public static <T extends CodeList<T>> T forCodeName(final Class<T> 
codeType, String name, final boolean canCreate) {
-        return CodeLists.forName(codeType, name, canCreate);
+        if (canCreate) {
+            return CodeLists.getOrCreate(codeType, name);
+        } else {
+            return forCodeName(codeType, name, null);
+        }
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/CodeListAdapter.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/CodeListAdapter.java
index e253257fb2..9adc3ef22a 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/CodeListAdapter.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/CodeListAdapter.java
@@ -18,7 +18,7 @@ package org.apache.sis.xml.bind.cat;
 
 import jakarta.xml.bind.annotation.adapters.XmlAdapter;
 import org.opengis.util.CodeList;
-import org.apache.sis.util.iso.Types;
+import org.apache.sis.util.privy.CodeLists;
 import org.apache.sis.xml.bind.Context;
 import org.apache.sis.xml.bind.FilterByVersion;
 
@@ -111,7 +111,12 @@ public abstract class CodeListAdapter<ValueType extends 
CodeListAdapter<ValueTyp
      */
     @Override
     public final BoundType unmarshal(final ValueType adapter) {
-        return (adapter != null) ? Types.forCodeName(getCodeListClass(), 
adapter.identifier.toString(), true) : null;
+        if (adapter != null) try {
+            return CodeLists.getOrCreate(getCodeListClass(), 
adapter.identifier.toString());
+        } catch (RuntimeException e) {
+            Context.warningOccured(Context.current(), CodeListAdapter.class, 
"unmarshal", e, true);
+        }
+        return null;
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gco/GO_CharacterString.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gco/GO_CharacterString.java
index a28472414f..a6c39e4cc2 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gco/GO_CharacterString.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gco/GO_CharacterString.java
@@ -37,6 +37,7 @@ import org.apache.sis.xml.privy.LegacyNamespaces;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Workaround;
 import org.apache.sis.util.iso.Types;
+import org.apache.sis.util.privy.CodeLists;
 import org.apache.sis.util.resources.IndexedResourceBundle;
 import org.apache.sis.util.resources.Messages;
 import org.apache.sis.util.resources.Errors;
@@ -294,17 +295,20 @@ public class GO_CharacterString {
             if (ct != null && CodeList.class.isAssignableFrom(ct)) {
                 final String attribute = 
e.getAttribute("codeListValue").trim();
                 if (!attribute.isEmpty()) {
-                    @SuppressWarnings("unchecked")
-                    final CodeList<?> c = Types.forCodeName((Class) ct, 
attribute, true);
-                    text = Types.getCodeTitle(c);
-                    type = ENUM;
+                    try {
+                        @SuppressWarnings("unchecked")
+                        final CodeList<?> c = CodeLists.getOrCreate((Class) 
ct, attribute);
+                        text = Types.getCodeTitle(c);
+                        type = ENUM;
+                    } catch (RuntimeException ex) {
+                        Context.warningOccured(Context.current(), 
GO_CharacterString.class, "setCodeList", ex, true);
+                    }
                     return;
-                } else {
-                    resources = Errors.class;
-                    errorKey  = Errors.Keys.MissingOrEmptyAttribute_2;
-                    args      = new Object[2];
-                    args[1]   = "codeListValue";
                 }
+                resources = Errors.class;
+                errorKey  = Errors.Keys.MissingOrEmptyAttribute_2;
+                args      = new Object[2];
+                args[1]   = "codeListValue";
             } else {
                 resources = Messages.class;
                 errorKey  = Messages.Keys.UnknownCodeList_1;
@@ -323,6 +327,7 @@ public class GO_CharacterString {
      * @return the character sequence for this {@code <gco:CharacterString>}.
      */
     protected CharSequence toCharSequence() {
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
         final CharSequence text = CharSequences.trimWhitespaces(this.text);
         if (text != null && (text.length() != 0 || text instanceof Anchor)) {  
     // Anchor may contain attributes.
             return text;
@@ -343,6 +348,7 @@ public class GO_CharacterString {
      */
     @Override
     public final String toString() {
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
         final CharSequence text = this.text;
         return (text != null) ? text.toString() : null;     // We really want 
to return null here.
     }
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/CodeListAdapter.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/CodeListAdapter.java
index af71e0654e..b3ee0ececb 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/CodeListAdapter.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/CodeListAdapter.java
@@ -21,6 +21,8 @@ import jakarta.xml.bind.annotation.XmlAttribute;
 import jakarta.xml.bind.annotation.adapters.XmlAdapter;
 import org.opengis.util.CodeList;
 import org.apache.sis.util.iso.Types;
+import org.apache.sis.util.privy.CodeLists;
+import org.apache.sis.xml.bind.Context;
 
 
 /**
@@ -84,11 +86,16 @@ public abstract class CodeListAdapter<BoundType extends 
CodeList<BoundType>> ext
      * contain the value. JAXB calls automatically this method at 
unmarshalling time.
      *
      * @param  identifier  the code space and identifier.
-     * @return a code list which represents the GML value.
+     * @return a code list which represents the GML value, or {@code null}.
      */
     @Override
     public final BoundType unmarshal(final Value identifier) {
-        return (identifier != null) ? Types.forCodeName(getCodeListClass(), 
identifier.value, true) : null;
+        if (identifier != null) try {
+            return CodeLists.getOrCreate(getCodeListClass(), identifier.value);
+        } catch (RuntimeException e) {
+            Context.warningOccured(Context.current(), CodeListAdapter.class, 
"unmarshal", e, true);
+        }
+        return null;
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/SC_VerticalCRS.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/SC_VerticalCRS.java
index 253ff231b6..e5bc159a05 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/SC_VerticalCRS.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/gml/SC_VerticalCRS.java
@@ -50,7 +50,7 @@ import org.apache.sis.util.resources.Errors;
  * }
  * }
  *
- * Next, the module shall provides the following:
+ * Next, the module shall provide the following:
  * <ul>
  *   <li>The path to {@code MyClass} shall be provided in the {@code 
module-info.java} file
  *       as a {@code org.apache.sis.xml.bind.AdapterReplacement} service.</li>
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/SensorType.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/SensorType.java
index 74bed1e955..2c96b8d2af 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/SensorType.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/metadata/replace/SensorType.java
@@ -16,8 +16,6 @@
  */
 package org.apache.sis.xml.bind.metadata.replace;
 
-import java.util.List;
-import java.util.ArrayList;
 import org.opengis.annotation.UML;
 import org.opengis.annotation.Specification;
 import org.opengis.util.CodeList;
@@ -37,72 +35,43 @@ public final class SensorType extends CodeList<SensorType> {
      */
     private static final long serialVersionUID = 3510875680392393838L;
 
-    /**
-     * List of all enumerations of this type.
-     * Must be declared before any enum declaration.
+    /*
+     * We need to construct values with `valueOf(String)` instead of the 
constructor
+     * because this package is not exported to GeoAPI. See `CodeList` class 
javadoc.
      */
-    private static final List<SensorType> VALUES = new ArrayList<>();
 
     /**
      * The sensor is a radiometer.
      */
-    public static final SensorType RADIOMETER = new SensorType("RADIOMETER");
+    public static final SensorType RADIOMETER = valueOf("RADIOMETER");
 
     /**
-     * Constructs an element of the given name. The new element is
-     * automatically added to the list returned by {@link #values()}.
+     * Constructs an element of the given name.
      *
      * @param  name  the name of the new element.
      *         This name must not be in use by another element of this type.
      */
     private SensorType(final String name) {
-        super(name, VALUES);
-    }
-
-    /**
-     * Returns the list of {@code SensorType}s.
-     *
-     * @return the list of codes declared in the current JVM.
-     */
-    public static SensorType[] values() {
-        synchronized (VALUES) {
-            return VALUES.toArray(SensorType[]::new);
-        }
-    }
-
-    /**
-     * Disables the search for UML identifiers because we do not export this 
package to GeoAPI.
-     *
-     * @return {@code null}.
-     */
-    @Override
-    public String identifier() {
-        return null;
+        super(name);
     }
 
     /**
      * Returns the list of codes of the same kind as this code list element.
-     * Invoking this method is equivalent to invoking {@link #values()}, 
except that
-     * this method can be invoked on an instance of the parent {@code 
CodeList} class.
      *
      * @return all code {@linkplain #values() values} for this code list.
      */
     @Override
     public SensorType[] family() {
-        return values();
+        return values(SensorType.class);
     }
 
     /**
-     * Returns the sensor type that matches the given string, or returns a
-     * new one if none match it. More specifically, this methods returns the 
first instance for
-     * which <code>{@linkplain #name() name()}.{@linkplain String#equals 
equals}(code)</code>
-     * returns {@code true}. If no existing instance is found, then a new one 
is created for
-     * the given name.
+     * Returns the sensor type that matches the given string, or returns a new 
one if none match it.
      *
      * @param  code  the name of the code to fetch or to create.
      * @return a code matching the given name.
      */
     public static SensorType valueOf(String code) {
-        return valueOf(SensorType.class, code);
+        return valueOf(SensorType.class, code, SensorType::new).get();
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeChildrenTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeChildrenTest.java
index d80f63a2b2..c776b19885 100644
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeChildrenTest.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/TreeNodeChildrenTest.java
@@ -183,7 +183,7 @@ public final class TreeNodeChildrenTest extends TestCase {
             "Some title",
             "First alternate title",
             "Some edition",
-            "PresentationForm[MAP_DIGITAL]",
+            "PresentationForm.MAP_DIGITAL",
             "Some other details"
         };
         assertEquals(-1, children.titleProperty);
@@ -205,8 +205,8 @@ public final class TreeNodeChildrenTest extends TestCase {
             "First alternate title",
             "Second alternate title",
             "Some edition",
-            "PresentationForm[MAP_DIGITAL]",
-            "PresentationForm[MAP_HARDCOPY]",
+            "PresentationForm.MAP_DIGITAL",
+            "PresentationForm.MAP_HARDCOPY",
             "Some other details"
         };
         assertEquals(-1, children.titleProperty);
@@ -234,7 +234,7 @@ public final class TreeNodeChildrenTest extends TestCase {
         final TreeNodeChildren children = (TreeNodeChildren) 
node.getChildren();
         final String[] expected = {
             // The "Date" node should be omitted because merged with the 
parent "Date" node.
-            "DateType[CREATION]"
+            "DateType.CREATION"
         };
         assertEquals(0, children.titleProperty);
         assertFalse (children.isEmpty());
@@ -260,9 +260,9 @@ public final class TreeNodeChildrenTest extends TestCase {
             "Second alternate title",
             "Third alternate title",                // After addition
             "New edition",                          // After "addition" 
(actually change).
-            "PresentationForm[IMAGE_DIGITAL]",      // After addition
-            "PresentationForm[MAP_DIGITAL]",
-            "PresentationForm[MAP_HARDCOPY]",
+            "PresentationForm.IMAGE_DIGITAL",       // After addition
+            "PresentationForm.MAP_DIGITAL",
+            "PresentationForm.MAP_HARDCOPY",
             "Some other details"
         };
         toAdd.setValue(TableColumn.IDENTIFIER, "edition");
@@ -358,8 +358,8 @@ public final class TreeNodeChildrenTest extends TestCase {
             null, // edition date
             null, // identifiers (collection)
             null, // cited responsibly parties (collection)
-            "PresentationForm[MAP_DIGITAL]",
-            "PresentationForm[MAP_HARDCOPY]",
+            "PresentationForm.MAP_DIGITAL",
+            "PresentationForm.MAP_HARDCOPY",
             null, // series
             "Some other details",
 //          null, // collective title                       -- deprecated as 
of ISO 19115:2014.
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultContactTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultContactTest.java
index 1eb3c935fd..a358d615bd 100644
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultContactTest.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/metadata/iso/citation/DefaultContactTest.java
@@ -101,7 +101,7 @@ public final class DefaultContactTest extends TestCase 
implements Filter {
          */
         assertSame(tel2, contact.getPhone());       // Shall ignore the 
TelephoneType.SMS.
         assertEquals("IgnoredPropertyAssociatedTo_1", resourceKey);
-        assertArrayEquals(new String[] {"TelephoneType[SMS]"}, parameters);
+        assertArrayEquals(new String[] {"TelephoneType.SMS"}, parameters);
         verifyLegacyLists(tels);
     }
 
diff --git 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/TypesTest.java
 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/TypesTest.java
index c34a78c918..59fdada10d 100644
--- 
a/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/TypesTest.java
+++ 
b/endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/TypesTest.java
@@ -147,18 +147,18 @@ public final class TypesTest extends TestCase {
      */
     @Test
     public void testForCodeName() {
-        assertSame(ImagingCondition.SEMI_DARKNESS, 
Types.forCodeName(ImagingCondition.class, "SEMI_DARKNESS", false));
-        assertSame(ImagingCondition.SEMI_DARKNESS, 
Types.forCodeName(ImagingCondition.class, "SEMIDARKNESS",  false));
-        assertSame(ImagingCondition.SEMI_DARKNESS, 
Types.forCodeName(ImagingCondition.class, "semi darkness", false));
-        assertSame(ImagingCondition.SEMI_DARKNESS, 
Types.forCodeName(ImagingCondition.class, "semi-darkness", false));
-        assertNull(Types.forCodeName(ImagingCondition.class, "darkness", 
false));
+        assertSame(ImagingCondition.SEMI_DARKNESS, 
Types.forCodeName(ImagingCondition.class, "SEMI_DARKNESS", null));
+        assertSame(ImagingCondition.SEMI_DARKNESS, 
Types.forCodeName(ImagingCondition.class, "SEMIDARKNESS",  null));
+        assertSame(ImagingCondition.SEMI_DARKNESS, 
Types.forCodeName(ImagingCondition.class, "semi darkness", null));
+        assertSame(ImagingCondition.SEMI_DARKNESS, 
Types.forCodeName(ImagingCondition.class, "semi-darkness", null));
+        assertNull(Types.forCodeName(ImagingCondition.class, "darkness", 
null));
 
-        assertSame(PixelInCell.CELL_CORNER, 
Types.forCodeName(PixelInCell.class, "cell corner", false));
-        assertSame(PixelInCell.CELL_CORNER, 
Types.forCodeName(PixelInCell.class, "cellCorner",  false));
-        assertSame(PixelInCell.CELL_CENTER, 
Types.forCodeName(PixelInCell.class, "cell center", false));
-        assertSame(PixelInCell.CELL_CENTER, 
Types.forCodeName(PixelInCell.class, "cellCenter",  false));
-        assertSame(PixelInCell.CELL_CENTER, 
Types.forCodeName(PixelInCell.class, "cell centre", false));
-        assertSame(PixelInCell.CELL_CENTER, 
Types.forCodeName(PixelInCell.class, "cellCentre",  false));
+        assertSame(PixelInCell.CELL_CORNER, 
Types.forCodeName(PixelInCell.class, "cell corner", null));
+        assertSame(PixelInCell.CELL_CORNER, 
Types.forCodeName(PixelInCell.class, "cellCorner",  null));
+        assertSame(PixelInCell.CELL_CENTER, 
Types.forCodeName(PixelInCell.class, "cell center", null));
+        assertSame(PixelInCell.CELL_CENTER, 
Types.forCodeName(PixelInCell.class, "cellCenter",  null));
+        assertSame(PixelInCell.CELL_CENTER, 
Types.forCodeName(PixelInCell.class, "cell centre", null));
+        assertSame(PixelInCell.CELL_CENTER, 
Types.forCodeName(PixelInCell.class, "cellCentre",  null));
     }
 
     /**
diff --git 
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
index 203f5e1cc8..0dbbbd3bc0 100644
--- 
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
@@ -1029,7 +1029,7 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
             }
             unit = defaultUnit;
         }
-        AxisDirection direction = Types.forCodeName(AxisDirection.class, 
orientation.keyword, true);
+        AxisDirection direction = Types.forCodeName(AxisDirection.class, 
orientation.keyword, AxisDirection::valueOf);
         final Element meridian = element.pullElement(OPTIONAL, 
WKTKeywords.Meridian);
         if (meridian != null) {
             double angle = meridian.pullDouble("meridian");
@@ -1576,7 +1576,7 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
         }
         final String name = element.pullString("name");
         final PixelInCell pixelInCell = Types.forCodeName(PixelInCell.class,
-                element.pullVoidElement("pixelInCell").keyword, true);
+                element.pullVoidElement("pixelInCell").keyword, 
PixelInCell::valueOf);
         final DatumFactory datumFactory = factories.getDatumFactory();
         try {
             return datumFactory.createImageDatum(parseAnchorAndClose(element, 
name), pixelInCell);
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DirectionAlongMeridian.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DirectionAlongMeridian.java
index fab0c65e7f..640690fe0b 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DirectionAlongMeridian.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/cs/DirectionAlongMeridian.java
@@ -169,7 +169,7 @@ final class DirectionAlongMeridian extends 
FormattableObject implements Comparab
             final String name = toString();
             direction = AxisDirections.valueOf(name);
             if (direction == null) {
-                direction = Types.forCodeName(AxisDirection.class, name, true);
+                direction = Types.forCodeName(AxisDirection.class, name, 
AxisDirection::valueOf);
             }
         }
         return direction;
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
index 93c573a7db..b7720a9ffd 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java
@@ -23,9 +23,10 @@ import org.opengis.util.GenericName;
 import org.opengis.referencing.datum.VerticalDatumType;
 import org.opengis.referencing.cs.CoordinateSystemAxis;
 import org.opengis.referencing.cs.AxisDirection;
-import org.apache.sis.util.StringBuilders;
-import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Characters;
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.StringBuilders;
+import org.apache.sis.util.privy.CodeLists;
 import org.apache.sis.measure.Units;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
@@ -187,7 +188,7 @@ public final class VerticalDatumTypes implements 
Predicate<CodeList<?>> {
             for (int i=0; i<name.length();) {
                 final int c = name.codePointAt(i);
                 if (Character.isLetter(c)) {
-                    return CodeList.valueOf(VerticalDatumType.class, new 
VerticalDatumTypes(name), null);
+                    return CodeLists.find(VerticalDatumType.class, new 
VerticalDatumTypes(name));
                 }
                 i += Character.charCount(c);
             }
diff --git 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/MetadataReader.java
 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/MetadataReader.java
index 232450ba92..ae58750f54 100644
--- 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/MetadataReader.java
+++ 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/MetadataReader.java
@@ -69,6 +69,7 @@ import org.apache.sis.system.Configuration;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.iso.Types;
 import org.apache.sis.util.privy.CollectionsExt;
+import org.apache.sis.util.privy.CodeLists;
 import org.apache.sis.util.privy.Strings;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.measure.Units;
@@ -269,11 +270,12 @@ split:  while ((start = 
CharSequences.skipLeadingWhitespaces(value, start, lengt
      * In the latter case, this method emits a warning.
      */
     private <T extends Enum<T>> T forEnumName(final Class<T> enumType, final 
String name) {
-        final T code = Types.forEnumName(enumType, name);
-        if (code == null && name != null) {
-            warning(Errors.Keys.UnknownEnumValue_2, enumType, name, null);
+        try {
+            return CodeLists.forEnumName(enumType, name);
+        } catch (IllegalArgumentException e) {
+            warning(Errors.Keys.UnknownEnumValue_2, enumType, name, e);
+            return null;
         }
-        return code;
     }
 
     /**
@@ -281,7 +283,7 @@ split:  while ((start = 
CharSequences.skipLeadingWhitespaces(value, start, lengt
      * In the latter case, this method emits a warning.
      */
     private <T extends CodeList<T>> T forCodeName(final Class<T> codeType, 
final String name) {
-        final T code = Types.forCodeName(codeType, name, false);
+        final T code = Types.forCodeName(codeType, name, null);
         if (code == null && name != null) {
             /*
              * CodeLists are not enums, but using the error message for enums 
is not completly wrong since
diff --git 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/Axis.java
 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/Axis.java
index ebd2896d8a..0f1799a1e0 100644
--- 
a/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/Axis.java
+++ 
b/endorsed/src/org.apache.sis.storage.netcdf/main/org/apache/sis/storage/netcdf/base/Axis.java
@@ -206,7 +206,7 @@ public final class Axis extends NamedElement {
          * give precedence to choices #2 and #3 in that order. Choice #1 is 
not considered authoritative
          * because it applies (in principle) only to vertical axis.
          */
-        AxisDirection dir = Types.forCodeName(AxisDirection.class, direction, 
false);
+        AxisDirection dir = Types.forCodeName(AxisDirection.class, direction, 
null);
         AxisDirection check = AxisDirections.fromAbbreviation(abbreviation);
         final boolean isSigned = (dir != null);     // Whether `dir` takes in 
account the direction of positive values.
         boolean isConsistent = true;
@@ -277,7 +277,7 @@ public final class Axis extends NamedElement {
                         case 'N': return AxisDirection.NORTH;
                     }
                 }
-                return Types.forCodeName(AxisDirection.class, direction, 
false);
+                return Types.forCodeName(AxisDirection.class, direction, null);
             }
         }
         return 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 e15ea38b65..73f5eae546 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
@@ -354,7 +354,7 @@ abstract class StringConverter<T> extends 
SystemConverter<String, T> {
 
         /** Converts the given string to the target type of this converter. */
         @Override T doConvert(final String source) {
-            final T code = CodeLists.forName(targetClass, source, false);
+            final T code = CodeLists.forCodeName(targetClass, source);
             if (code == null) {
                 throw new 
UnconvertibleObjectException(formatErrorMessage(source));
             }
@@ -388,11 +388,11 @@ abstract class StringConverter<T> extends 
SystemConverter<String, T> {
 
         /** Converts the given string to the target type of this converter. */
         @Override T doConvert(final String source) {
-            final T code = CodeLists.forName(targetClass, source);
-            if (code == null) {
-                throw new 
UnconvertibleObjectException(formatErrorMessage(source));
+            try {
+                return CodeLists.forEnumName(targetClass, source);
+            } catch (IllegalArgumentException e) {
+                throw new 
UnconvertibleObjectException(formatErrorMessage(source), e);
             }
-            return code;
         }
 
         /** Invoked by the constructor for creating the inverse converter. */
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/CodeListSet.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/CodeListSet.java
index 95c9c2ff39..92e0be6861 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/CodeListSet.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/collection/CodeListSet.java
@@ -24,7 +24,6 @@ import java.util.NoSuchElementException;
 import java.io.Serializable;
 import java.lang.reflect.Modifier;
 import org.opengis.util.CodeList;
-import org.apache.sis.util.privy.CodeLists;
 import org.apache.sis.util.privy.CheckedArrayList;
 import org.apache.sis.util.resources.Errors;
 
@@ -136,7 +135,7 @@ public class CodeListSet<E extends CodeList<E>> extends 
AbstractSet<E>
     public CodeListSet(final Class<E> elementType, final boolean fill) throws 
IllegalArgumentException {
         this(elementType);
         if (fill) {
-            codes = POOL.unique(CodeLists.values(elementType));
+            codes = POOL.unique(CodeList.values(elementType));
             int n = codes.length;
             if (n < Long.SIZE) {
                 values = (1L << n) - 1;
@@ -167,7 +166,7 @@ public class CodeListSet<E extends CodeList<E>> extends 
AbstractSet<E>
     final E valueOf(final int ordinal) {
         E[] array = codes;
         if (array == null || ordinal >= array.length) {
-            codes = array = POOL.unique(CodeLists.values(elementType));
+            codes = array = POOL.unique(CodeList.values(elementType));
         }
         return array[ordinal];
     }
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/CodeLists.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/CodeLists.java
index 87c842258e..323e2cb003 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/CodeLists.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/CodeLists.java
@@ -16,15 +16,16 @@
  */
 package org.apache.sis.util.privy;
 
-import java.lang.reflect.Array;
 import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.InaccessibleObjectException;
 import java.lang.reflect.UndeclaredThrowableException;
+import java.util.function.Predicate;
 import org.opengis.util.CodeList;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Characters.Filter;
+import org.apache.sis.util.resources.Errors;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
-import java.util.function.Predicate;
 import org.opengis.util.ControlledVocabulary;
 
 
@@ -42,6 +43,8 @@ public final class CodeLists implements 
Predicate<CodeList<?>> {
 
     /**
      * Creates a new filter for the specified code name.
+     *
+     * @param codename the name to compare during filtering operation.
      */
     private CodeLists(final String codename) {
         this.codename  = codename;
@@ -64,52 +67,28 @@ public final class CodeLists implements 
Predicate<CodeList<?>> {
 
     /**
      * Returns {@code true} if the given names matches the name we are looking 
for.
-     * This is defined in a separated method in order to ensure that all code 
paths
-     * use the same criterion.
      */
     private static boolean accept(final String candidate, final String 
codename) {
         return CharSequences.equalsFiltered(candidate, codename, 
Filter.LETTERS_AND_DIGITS, true);
     }
 
     /**
-     * Returns the code of the given type that matches the given name,
-     * or optionally returns a new one if none match the name.
-     *
-     * @param  <T>        the compile-time type given as the {@code codeType} 
parameter.
-     * @param  codeType   the type of code list.
-     * @param  name       the name of the code to obtain, or {@code null}.
-     * @param  canCreate  {@code true} if this method is allowed to create new 
code.
-     * @return a code matching the given name, or {@code null}.
-     *
-     * @see org.apache.sis.util.iso.Types#forCodeName(Class, String, boolean)
-     */
-    public static <T extends CodeList<T>> T forName(final Class<T> codeType, 
String name, final boolean canCreate) {
-        name = Strings.trimOrNull(name);
-        if (name == null) {
-            return null;
-        }
-        return CodeList.valueOf(codeType, new CodeLists(name), canCreate ? 
name : null);
-    }
-
-    /**
-     * Returns the enumeration value of the given type that matches the given 
name, or {@code null} if none.
+     * Returns the enumeration value of the given type that matches the given 
name.
      *
      * @param  <T>       the compile-time type given as the {@code enumType} 
parameter.
      * @param  enumType  the type of enumeration.
      * @param  name      the name of the enumeration value to obtain, or 
{@code null}.
-     * @return a value matching the given name, or {@code null}.
+     * @return a value matching the given name, or {@code null} if the given 
name was null or blank.
+     * @throws IllegalArgumentException if no enumeration value matches the 
given name.
      *
      * @see org.apache.sis.util.iso.Types#forEnumName(Class, String)
      */
-    public static <T extends Enum<T>> T forName(final Class<T> enumType, 
String name) {
+    public static <T extends Enum<T>> T forEnumName(final Class<T> enumType, 
String name) {
         name = Strings.trimOrNull(name);
         if (name != null) try {
             return Enum.valueOf(enumType, name);
         } catch (IllegalArgumentException e) {
             final T[] values = enumType.getEnumConstants();
-            if (values == null) {
-                throw e;
-            }
             if (values instanceof ControlledVocabulary[]) {
                 for (final ControlledVocabulary code : 
(ControlledVocabulary[]) values) {
                     for (final String candidate : code.names()) {
@@ -118,31 +97,67 @@ public final class CodeLists implements 
Predicate<CodeList<?>> {
                         }
                     }
                 }
-            } else {
+            } else if (values != null) {
                 for (final Enum<?> code : values) {
                     if (accept(code.name(), name)) {
                         return enumType.cast(code);
                     }
                 }
             }
+            throw e;
         }
         return null;
     }
 
     /**
-     * Returns all known values for the given type of code list or enumeration.
+     * Returns the code of the given type that matches the given name.
      *
-     * @param  <T>       the compile-time type given as the {@code codeType} 
parameter.
-     * @param  codeType  the type of code list or enumeration.
-     * @return the list of values for the given code list or enumeration, or 
an empty array if none.
+     * @param  <E>       the compile-time type given as the {@code codeType} 
parameter.
+     * @param  codeType  the type of code list.
+     * @param  name      the name of the code to obtain, or {@code null}.
+     * @return a code matching the given name, or {@code null} if none.
+     */
+    public static <E extends CodeList<E>> E forCodeName(final Class<E> 
codeType, String name) {
+        name = Strings.trimOrNull(name);
+        return (name != null) ? find(codeType, new CodeLists(name)) : null;
+    }
+
+    /**
+     * Returns the code of the given type that matches the filter.
      *
-     * @see org.apache.sis.util.iso.Types#getCodeValues(Class)
+     * @param  <E>       the compile-time type given as the {@code codeType} 
parameter.
+     * @param  codeType  the type of code list.
+     * @param  filter    the criterion for selecting a code list.
+     * @return a code matching the given name, or {@code null} if none.
      */
-    @SuppressWarnings("unchecked")
-    public static <T extends ControlledVocabulary> T[] values(final Class<T> 
codeType) {
-        Object values;
-        try {
-            values = codeType.getMethod("values", (Class<?>[]) 
null).invoke(null, (Object[]) null);
+    public static <E extends CodeList<E>> E find(final Class<E> codeType, 
final Predicate<? super CodeList<?>> filter) {
+        for (final E code : CodeList.values(codeType)) {
+            if (filter.test(code)) {
+                return code;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the code of the given type that matches the given name, or 
creates a new code if none match the name.
+     * This method depends on reflection and should be avoided as much as 
possible.
+     * However, at this time we have no way to avoid this method completely.
+     *
+     * @param  <E>       the compile-time type given as the {@code codeType} 
parameter.
+     * @param  codeType  the type of code list.
+     * @param  name      the name of the code to obtain, or {@code null}.
+     * @return a code matching the given name, or {@code null} if the given 
name was null or blank.
+     * @throws IllegalArgumentException if no code value value matches the 
given name and new code cannot be created.
+     */
+    public static <E extends CodeList<E>> E getOrCreate(final Class<E> 
codeType, String name) {
+        name = Strings.trimOrNull(name);
+        if (name == null) {
+            return null;
+        }
+        E code = forCodeName(codeType, name);
+        if (code == null) try {
+            code = codeType.cast(codeType.getMethod("valueOf", 
String.class).invoke(null, name));
         } catch (InvocationTargetException e) {
             final Throwable cause = e.getCause();
             if (cause instanceof RuntimeException) {
@@ -151,10 +166,13 @@ public final class CodeLists implements 
Predicate<CodeList<?>> {
             if (cause instanceof Error) {
                 throw (Error) cause;
             }
+            // `CodeList.valueOf(String)` methods are not expected to throw 
checked exceptions.
             throw new UndeclaredThrowableException(cause);
-        } catch (NoSuchMethodException | IllegalAccessException e) {
-            values = Array.newInstance(codeType, 0);
+        } catch (IllegalAccessException e) {
+            throw (InaccessibleObjectException) new 
InaccessibleObjectException(e.getMessage()).initCause(e);
+        } catch (NoSuchMethodException | NullPointerException e) {
+            throw new 
IllegalArgumentException(Errors.format(Errors.Keys.ElementNotFound_1, name), e);
         }
-        return (T[]) values;
+        return code;
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/CodeListSetTest.java
 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/CodeListSetTest.java
index 5a20b6cfb3..6f449ee356 100644
--- 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/CodeListSetTest.java
+++ 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/CodeListSetTest.java
@@ -213,7 +213,7 @@ public final class CodeListSetTest extends TestCase {
     public void testFill() {
         final CodeListSet<AxisDirection> c = new 
CodeListSet<>(AxisDirection.class, true);
         assertTrue(c.size() >= 32, "Expect at least 32 elements as of GeoAPI 
3.0.");
-        assertTrue(c.toString().startsWith("[AxisDirection[OTHER], 
AxisDirection[NORTH], "));
+        assertTrue(c.toString().startsWith("[AxisDirection.OTHER, 
AxisDirection.NORTH, "));
         /*
          * Testing the full array would be too long and may change in future 
GeoAPI version
          * anyway. Actually the main interest of this test is to ensure that 
the toString()
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/LargeCodeList.java
 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/LargeCodeList.java
index 0eb8405306..17954d32fb 100644
--- 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/LargeCodeList.java
+++ 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/util/collection/LargeCodeList.java
@@ -16,8 +16,6 @@
  */
 package org.apache.sis.util.collection;
 
-import java.util.List;
-import java.util.ArrayList;
 import org.opengis.util.CodeList;
 
 // Test dependencies
@@ -31,27 +29,23 @@ import static org.junit.jupiter.api.Assertions.*;
  * @author  Martin Desruisseaux (Geomatys)
  */
 @SuppressWarnings("serial")
-public final class LargeCodeList  extends CodeList<LargeCodeList> {
-    /**
-     * List of all enumerations of this type.
-     */
-    private static final List<LargeCodeList> VALUES = new ArrayList<>(100);
-
+public final class LargeCodeList extends CodeList<LargeCodeList> {
     /**
      * Creates 100 code list elements.
+     * We need to construct values with {@code valueOf(String)} instead of the 
constructor
+     * because this package is not exported to GeoAPI. See {@link CodeList} 
class javadoc.
      */
     static {
-        for (int i=0; i<100; i++) {
-            assertEquals(i, new LargeCodeList(i).ordinal());
+        for (int i=0; i<80; i++) {
+            assertEquals(i, valueOf("LC#" + i).ordinal());
         }
     }
 
     /**
-     * Constructs an element. The new element is automatically
-     * added to the list to be returned by {@link #values}.
+     * Constructs an element.
      */
-    private LargeCodeList(final int i) {
-        super("LC#" + i, VALUES);
+    private LargeCodeList(String name) {
+        super(name);
     }
 
     /**
@@ -60,9 +54,7 @@ public final class LargeCodeList  extends 
CodeList<LargeCodeList> {
      * @return the list of codes declared in the current JVM.
      */
     public static LargeCodeList[] values() {
-        synchronized (VALUES) {
-            return VALUES.toArray(LargeCodeList[]::new);
-        }
+        return values(LargeCodeList.class);
     }
 
     /**
@@ -76,13 +68,12 @@ public final class LargeCodeList  extends 
CodeList<LargeCodeList> {
     }
 
     /**
-     * Returns the axis code that matches the given string,
-     * or returns a new one if none match it.
+     * Returns the code that matches the given string, or returns a new one if 
none match it.
      *
      * @param  code  the name of the code list element to fetch or to create.
      * @return a code list element matching the given name.
      */
     public static LargeCodeList valueOf(final String code) {
-        return valueOf(LargeCodeList.class, code);
+        return valueOf(LargeCodeList.class, code, LargeCodeList::new).get();
     }
 }
diff --git a/geoapi/snapshot b/geoapi/snapshot
index c786929b56..b2c419d046 160000
--- a/geoapi/snapshot
+++ b/geoapi/snapshot
@@ -1 +1 @@
-Subproject commit c786929b56e8f8aed8eceb0144734bbd7ac28ea6
+Subproject commit b2c419d046315ef580eb8eb746bb20361df79f7d
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/PositionableProjection.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/PositionableProjection.java
index 61a92019c1..0f7237ee39 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/PositionableProjection.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/PositionableProjection.java
@@ -16,8 +16,6 @@
  */
 package org.apache.sis.gui.referencing;
 
-import java.util.List;
-import java.util.ArrayList;
 import org.opengis.util.CodeList;
 import org.opengis.util.FactoryException;
 import org.opengis.geometry.DirectPosition;
@@ -45,17 +43,11 @@ import static org.apache.sis.gui.internal.LogHandler.LOGGER;
  * The point of interest is typically determined by mouse location.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   1.1
  */
 @SuppressWarnings("serial")         // We do not guarantee serialization 
compatibility.
 public abstract class PositionableProjection extends 
CodeList<PositionableProjection> {
-    /**
-     * List of all enumerations of this type.
-     * Must be declared before any enum declaration.
-     */
-    private static final List<PositionableProjection> VALUES = new 
ArrayList<>(1);
-
     /**
      * Provides <cite>Orthographic</cite> projection centered on a point of 
interest.
      */
@@ -134,22 +126,18 @@ public abstract class PositionableProjection extends 
CodeList<PositionableProjec
     private final short nameKey;
 
     /**
-     * Constructs an element of the given name. The new element is 
automatically added to the list
-     * returned by {@link #values()}. Subclasses shall ensure that only one 
instance is created for
-     * each value because there is no mechanism for removing previously 
created values.
+     * Constructs an element of the given name.
      *
-     * @param name  the name of the new element. This name shall not be in use 
by another element of this type.
-     */
-    protected PositionableProjection(final String name) {
-        super(name, VALUES);
-        nameKey = 0;
-    }
-
-    /**
-     * Creates a new enumeration.
+     * <h4>Design note</h4>
+     * We do not provide public or protected constructor because code lists 
should be final,
+     * except for anonymous classes. Otherwise, {@link CodeList} constructor 
associate codes
+     * to the wrong class, as seen from the values returned by {@link 
#values(Class)}.
+     *
+     * @param name     the name of the new element. This name shall not be in 
use by another element of this type.
+     * @param nameKey  the projection name as a {@link Resources} keys.
      */
-    private PositionableProjection(final String name, final short nameKey) {
-        super(name, VALUES);
+    PositionableProjection(final String name, final short nameKey) {
+        super(name);
         this.nameKey = nameKey;
     }
 
@@ -159,9 +147,7 @@ public abstract class PositionableProjection extends 
CodeList<PositionableProjec
      * @return the list of codes declared in the current JVM.
      */
     public static PositionableProjection[] values() {
-        synchronized (VALUES) {
-            return VALUES.toArray(PositionableProjection[]::new);
-        }
+        return values(PositionableProjection.class);
     }
 
     /**
@@ -176,16 +162,6 @@ public abstract class PositionableProjection extends 
CodeList<PositionableProjec
         return values();
     }
 
-    /**
-     * Disables the search for UML identifiers because we do not export this 
package to GeoAPI.
-     *
-     * @return {@code null}.
-     */
-    @Override
-    public String identifier() {
-        return null;
-    }
-
     /**
      * Returns a name for this enumeration which can be used in a user 
interface.
      *
diff --git 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/package-info.java
 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/package-info.java
index b403df321f..eda03d9a69 100644
--- 
a/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/package-info.java
+++ 
b/optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/package-info.java
@@ -20,7 +20,7 @@
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.4
+ * @version 1.5
  * @since   1.1
  */
 package org.apache.sis.gui.referencing;

Reply via email to