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;