This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/sis.git
commit f636f7550492e46c26c1897d4a2688795ad034be Merge: 2b40ccfdc4 b8b49e9bc2 Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Tue Mar 12 20:06:26 2024 +0100 Merge branch 'geoapi-3.1'. .../metadata/iso/distribution/DefaultMedium.java | 2 +- .../apache/sis/metadata/sql/MetadataFallback.java | 9 +- .../apache/sis/metadata/sql/MetadataSource.java | 12 ++- .../main/org/apache/sis/util/iso/Types.java | 68 +++++++++++++-- .../apache/sis/xml/bind/cat/CodeListAdapter.java | 8 +- .../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 | 6 +- .../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 | 25 ++---- .../apache/sis/storage/netcdf/MetadataReader.java | 12 +-- .../org/apache/sis/storage/netcdf/base/Axis.java | 4 +- .../org/apache/sis/converter/StringConverter.java | 10 +-- .../main/org/apache/sis/util/privy/CodeLists.java | 98 ++++++++++++++-------- .../apache/sis/util/collection/LargeCodeList.java | 3 +- .../gui/referencing/PositionableProjection.java | 24 +++--- .../apache/sis/gui/referencing/package-info.java | 2 +- 20 files changed, 225 insertions(+), 123 deletions(-) diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataFallback.java index 5fb4e3cf41,f2e9856607..5a97fc34f9 --- 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 @@@ -79,8 -80,12 +80,12 @@@ final class MetadataFallback extends Me ArgumentChecks.ensureNonNull("type", type); ArgumentChecks.ensureNonEmpty("identifier", identifier); Object value; - if (ControlledVocabulary.class.isAssignableFrom(type)) { + if (CodeList.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 --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/metadata/sql/MetadataSource.java index 048e59d276,49ad8d16ea..b49468feb2 --- 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 @@@ -877,8 -878,12 +878,12 @@@ public class MetadataSource implements */ private Object lookup(final Class<?> type, final String identifier, boolean verify) throws MetadataStoreException { Object value; - if (ControlledVocabulary.class.isAssignableFrom(type)) { + if (CodeList.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,10 -1065,17 +1065,13 @@@ /** * 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) CodeLists.forEnumName(type.asSubclass(Enum.class), name); - } else { - return CodeLists.getOrCreate(type.asSubclass(CodeList.class), name); - } + static CodeList<?> getCodeList(final Class<?> type, final String name) { - return Types.forCodeName(type.asSubclass(CodeList.class), name, true); ++ return CodeLists.getOrCreate(type.asSubclass(CodeList.class), name); } /** diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/util/iso/Types.java index f2185e3b77,ace814308b..c9de32dd5a --- 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 @@@ -465,14 -465,30 +468,18 @@@ public final class Types extends Stati * Note that the size of the returned array may growth between different invocations of this method, * since users can add their own codes to an existing list. * - * <h4>Performance note</h4> - * This method works with both {@link Enum} and {@link CodeList}. However if the type is known to be - * an {@code Enum}, then the standard {@link Class#getEnumConstants()} method is more efficient. - * * @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 codeType the type of code list. + * @return the list of values for the given code list, 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) { - 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); + public static <T extends CodeList<?>> T[] getCodeValues(final Class<T> codeType) { + return CodeLists.values(codeType); } /** diff --cc endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/bind/cat/CodeListAdapter.java index 97a095e51f,4de8e1f5e3..3ce8db0d64 --- 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,9 -18,10 +18,10 @@@ 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; -import org.apache.sis.util.iso.Types; /** diff --cc endorsed/src/org.apache.sis.metadata/test/org/apache/sis/util/iso/TypesTest.java index 765fbcb650,59fdada10d..76cc0f751c --- 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 @@@ -134,21 -147,18 +134,21 @@@ public final class TypesTest extends Te */ @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_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)); + + if (PENDING_NEXT_GEOAPI_RELEASE) { - assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cell centre", false)); - assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cellCentre", false)); ++ assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cell centre", null)); ++ assertSame(PixelInCell.CELL_CENTER, Types.forCodeName(PixelInCell.class, "cellCentre", null)); + } } /** diff --cc endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/VerticalDatumTypes.java index c0f1fc1107,b7720a9ffd..b6de9fa530 --- 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 @@@ -17,17 -17,21 +17,19 @@@ package org.apache.sis.referencing.internal; import java.util.Collection; ++import java.util.function.Predicate; import javax.measure.Unit; import org.opengis.util.CodeList; 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: -import java.util.function.Predicate; - /** * Extensions to the standard set of {@link VerticalDatumType}. diff --cc endorsed/src/org.apache.sis.util/main/org/apache/sis/util/privy/CodeLists.java index b96c511e97,323e2cb003..7843f929d2 --- 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,18 -16,22 +16,20 @@@ */ 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 org.opengis.util.ControlledVocabulary; - /** * Implementation of some {@link org.apache.sis.util.iso.Types} methods needed by {@code org.apache.sis.util} module. - * This class opportunistically implements {@link CodeList.Filter} interface, but this should be considered - * an implementation details. - * This class opportunistically implements {@link Predicate} interface, but this is an implementation details. ++ * This class opportunistically implements {@code CodeList.Filter} interface, but this is an implementation details. * * @author Martin Desruisseaux (Geomatys) */ @@@ -43,27 -41,13 +45,29 @@@ public final class CodeLists implement */ private final String codename; + /** + * {@code true} if {@link CodeList#valueOf} is allowed to create new code lists. + */ + private final boolean canCreate; + /** * Creates a new filter for the specified code name. + * + * @param codename the name to compare during filtering operation. */ - private CodeLists(final String codename) { + private CodeLists(final String codename, final boolean canCreate) { this.codename = codename; + this.canCreate = canCreate; + } + + /** + * Returns the name of the code to create, or {@code null} if no new code list shall be created. + * + * @return the name specified at construction time. + */ + @Override + public String codename() { + return canCreate ? codename : null; } /** @@@ -126,32 -89,75 +109,77 @@@ return Enum.valueOf(enumType, name); } catch (IllegalArgumentException e) { final T[] values = enumType.getEnumConstants(); - if (values == null) { - throw e; - } - for (final Enum<?> code : values) { - if (accept(code.name(), name)) { - return enumType.cast(code); - if (values instanceof ControlledVocabulary[]) { - for (final ControlledVocabulary code : (ControlledVocabulary[]) values) { - for (final String candidate : code.names()) { - if (accept(candidate, name)) { - return enumType.cast(code); - } - } - } - } else if (values != null) { ++ if (values != null) { + for (final Enum<?> code : values) { + if (accept(code.name(), name)) { + return enumType.cast(code); + } } } + throw e; } return null; } + /** + * Returns the code of the given type that matches the given name. + * + * @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; ++ return (name != null) ? CodeList.valueOf(codeType, new CodeLists(name, false)) : null; + } + + /** + * Returns the code of the given type that matches the filter. + * + * @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. + */ + 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; ++ return CodeList.valueOf(codeType, new CodeList.Filter() { ++ @Override public boolean accept(CodeList<?> code) {return filter.test(code);} ++ @Override public String codename() {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)); ++ return CodeList.valueOf(codeType, new CodeLists(name, true)); ++ } ++ + /** + * Returns all known values for the given type of code list or enumeration. + * + * @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. - * - * @see org.apache.sis.util.iso.Types#getCodeValues(Class) + */ + @SuppressWarnings("unchecked") + public static <T extends CodeList<?>> T[] values(final Class<T> codeType) { + Object values; + try { + values = codeType.getMethod("values", (Class<?>[]) null).invoke(null, (Object[]) null); } catch (InvocationTargetException e) { final Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { diff --cc optional/src/org.apache.sis.gui/main/org/apache/sis/gui/referencing/PositionableProjection.java index 61a92019c1,0f7237ee39..ab5b9b262e --- 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 @@@ -134,22 -126,18 +134,18 @@@ public abstract class PositionableProje 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) { + PositionableProjection(final String name, final short nameKey) { - super(name); + super(name, VALUES); this.nameKey = nameKey; }