You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2022/11/13 16:30:41 UTC

[sis] branch geoapi-4.0 updated (4f2555bbe9 -> 7b995facc0)

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

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


    from 4f2555bbe9 Add a `Classes.boundOfParameterizedDeclaration(…)\ method and use it for making `ParameterDescriptor.getValueType()` conform to specification, which is to return the type elements when using array or collection.
     new f06c086e84 Generalize `Numbers.isNumber(Class)` by checking also for types unknown to this static method.
     new 7b995facc0 Save the "TypeName to Java class" association in `DefaultTypeName`.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/sis/util/iso/DefaultNameFactory.java    |  76 +++++---
 .../org/apache/sis/util/iso/DefaultTypeName.java   | 195 ++++++++++++---------
 .../main/java/org/apache/sis/util/iso/Names.java   |  43 +++--
 .../java/org/apache/sis/util/iso/TypeNames.java    |  35 ++--
 .../java/org/apache/sis/util/iso/NamesTest.java    |  35 ++--
 .../org/apache/sis/util/iso/TypeNamesTest.java     |  10 +-
 .../src/main/java/org/apache/sis/util/Numbers.java |   9 +-
 .../org/apache/sis/util/UnknownNameException.java  |   7 +-
 8 files changed, 224 insertions(+), 186 deletions(-)


[sis] 02/02: Save the "TypeName to Java class" association in `DefaultTypeName`.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 7b995facc0023ba36a3fb1785be1b49e2ffcd29a
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sun Nov 13 17:27:51 2022 +0100

    Save the "TypeName to Java class" association in `DefaultTypeName`.
---
 .../apache/sis/util/iso/DefaultNameFactory.java    |  76 +++++---
 .../org/apache/sis/util/iso/DefaultTypeName.java   | 195 ++++++++++++---------
 .../main/java/org/apache/sis/util/iso/Names.java   |  43 +++--
 .../java/org/apache/sis/util/iso/TypeNames.java    |  33 ++--
 .../java/org/apache/sis/util/iso/NamesTest.java    |  35 ++--
 .../org/apache/sis/util/iso/TypeNamesTest.java     |  10 +-
 .../org/apache/sis/util/UnknownNameException.java  |   7 +-
 7 files changed, 218 insertions(+), 181 deletions(-)

diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java
index a7557d787a..c093d2d7b4 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultNameFactory.java
@@ -22,6 +22,7 @@ import java.util.Arrays;
 import java.util.Locale;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.lang.reflect.Type;
 import org.opengis.metadata.Identifier;
 import org.opengis.util.TypeName;
 import org.opengis.util.NameSpace;
@@ -32,6 +33,7 @@ import org.opengis.util.NameFactory;
 import org.opengis.util.InternationalString;
 import org.apache.sis.util.SimpleInternationalString;
 import org.apache.sis.util.DefaultInternationalString;
+import org.apache.sis.util.UnknownNameException;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.WeakHashSet;
@@ -70,7 +72,7 @@ import org.apache.sis.internal.util.Strings;
  * from multiple threads.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.5
+ * @version 1.3
  *
  * @see Names
  * @see DefaultNameSpace
@@ -198,27 +200,50 @@ public class DefaultNameFactory extends AbstractFactory implements NameFactory {
     }
 
     /**
-     * Creates a type name from the given character sequence.
+     * Creates a type name from the given character sequence and automatically inferred Java type.
      * The default implementation returns a new or an existing {@link DefaultTypeName} instance.
+     * See {@link DefaultTypeName} javadoc for the list of recognized type names.
      *
-     * @param  scope  the {@linkplain AbstractName#scope() scope} of the type name to be created,
+     * @param  scope  the {@linkplain AbstractName#scope() scope} of the type name create,
      *                or {@code null} for a global namespace.
      * @param  name   the type name as a string or an international string.
-     * @return the type name for the given character sequence.
+     * @return the type name for the given scope and character sequence.
+     * @throws UnknownNameException if a mapping from the name to a Java class was expected to exist
+     *         (because the specified scope is "OGC" or "class") but the associated Java class can not be found.
      *
      * @see #toTypeName(Class)
+     * @see DefaultTypeName#DefaultTypeName(NameSpace, CharSequence)
      * @see Names#createTypeName(CharSequence, String, CharSequence)
      */
     @Override
-    public TypeName createTypeName(final NameSpace scope, final CharSequence name) {
+    public TypeName createTypeName(final NameSpace scope, final CharSequence name) throws UnknownNameException {
         return pool.unique(new DefaultTypeName(scope, name));
     }
 
+    /**
+     * Creates a type name from the given character sequence and explicit Java type.
+     * The default implementation returns a new or an existing {@link DefaultTypeName} instance.
+     *
+     * @param  scope     the {@linkplain AbstractName#scope() scope} of the type name to create,
+     *                   or {@code null} for a global namespace.
+     * @param  name      the type name as a string or an international string.
+     * @param  javaType  the value type to be returned by {@link #toJavaType()}, or {@code null} if none.
+     * @return the type name for the given scope, character sequence and Java type.
+     *
+     * @see #toTypeName(Class)
+     * @see DefaultTypeName#DefaultTypeName(NameSpace, CharSequence, Type)
+     *
+     * @since 1.3
+     */
+    public TypeName createTypeName(final NameSpace scope, final CharSequence name, final Type javaType) {
+        return pool.unique(new DefaultTypeName(scope, name, javaType));
+    }
+
     /**
      * Creates a member name from the given character sequence and attribute type.
      * The default implementation returns a new or an existing {@link DefaultMemberName} instance.
      *
-     * @param  scope  the {@linkplain AbstractName#scope() scope} of the member name to be created,
+     * @param  scope  the {@linkplain AbstractName#scope() scope} of the member name to create,
      *                or {@code null} for a global namespace.
      * @param  name   the member name as a string or an international string.
      * @param  attributeType  the type of the data associated with the record member.
@@ -235,7 +260,7 @@ public class DefaultNameFactory extends AbstractFactory implements NameFactory {
      * Creates a local name from the given character sequence.
      * The default implementation returns a new or an existing {@link DefaultLocalName} instance.
      *
-     * @param  scope  the {@linkplain AbstractName#scope() scope} of the local name to be created,
+     * @param  scope  the {@linkplain AbstractName#scope() scope} of the local name to create,
      *                or {@code null} for a global namespace.
      * @param  name   the local name as a string or an international string.
      * @return the local name for the given character sequence.
@@ -261,7 +286,7 @@ public class DefaultNameFactory extends AbstractFactory implements NameFactory {
      * array is 1, or an instance of {@link DefaultScopedName} if the length of the array is 2
      * or more.
      *
-     * @param  scope        the {@linkplain AbstractName#scope() scope} of the generic name to be created,
+     * @param  scope        the {@linkplain AbstractName#scope() scope} of the generic name to create,
      *                      or {@code null} for a global namespace.
      * @param  parsedNames  the local names as an array of {@link String} or {@link InternationalString} instances.
      *                      This array shall contain at least one element.
@@ -283,7 +308,7 @@ public class DefaultNameFactory extends AbstractFactory implements NameFactory {
      * This method splits the given name around a separator inferred from the given scope, or the
      * {@link DefaultNameSpace#DEFAULT_SEPARATOR ':'} separator if the given scope is null.
      *
-     * @param  scope  the {@linkplain AbstractName#scope() scope} of the generic name to be created,
+     * @param  scope  the {@linkplain AbstractName#scope() scope} of the generic name to create,
      *                or {@code null} for a global namespace.
      * @param  name   the qualified name, as a sequence of names separated by a scope-dependent separator.
      * @return a name parsed from the given string.
@@ -409,15 +434,12 @@ public class DefaultNameFactory extends AbstractFactory implements NameFactory {
     /**
      * Suggests a type name for the given class. Apache SIS provides a mapping between {@code Class}
      * and {@code TypeName} objects as documented in the {@link DefaultTypeName} javadoc.
-     *
-     * <p>In order to protect against potential changes in the {@code Class} ↔ {@code TypeName} mapping, users are
-     * encouraged to retrieve the {@code valueClass} by invoking the {@link Names#toClass(TypeName)} method instead
-     * than parsing the name.</p>
+     * The given {@code valueClass} can be fetched back by {@link DefaultTypeName#toJavaType()}.
      *
      * @param  valueClass  the Java class for which to get a type name, or {@code null}.
      * @return a suggested type name, or {@code null} if the given class was null.
      *
-     * @see DefaultTypeName#toClass()
+     * @see DefaultTypeName#toJavaType()
      * @see Names#toClass(TypeName)
      * @see Names#createTypeName(Class)
      *
@@ -431,26 +453,26 @@ public class DefaultNameFactory extends AbstractFactory implements NameFactory {
          * Note: we do not cache the TypeName for the valueClass argument because:
          *
          *  - It is not needed (at least in the default implementation) for getting unique instance.
-         *  - It is not the best place for performance improvement, since TypeName are usually only
-         *    a step in the creation of bigger object (typically AttributeType). Users are better to
-         *    cache the bigger object instead.
+         *  - It is not the best place for performance improvement, because `TypeName` is usually
+         *    only a step in the creation of bigger object (typically `AttributeType`).
+         *    Callers should cache the bigger object instead.
          */
-        TypeNames t = typeNames;
-        if (t == null) {
+        TypeNames mapper = typeNames;
+        if (mapper == null) {
             /*
-             * Create TypeNames outide the synchronized block because the TypeNames constructor will call back
-             * methods from this class. Since those methods are overrideable, this could invoke user's code.
-             * Note also that methods in this class use the `pool`, which is itself synchronized, so we are
-             * better to avoid double synchronization for reducing the risk of dead-lock.
+             * Create TypeNames outside the synchronized block because the TypeNames constructor will call back
+             * methods from this class. Since those methods are overrideable, they could invoke user's code.
+             * Note also that methods in this class use the `pool`, which is itself synchronized,
+             * so we are better to avoid double synchronization for reducing the risk of dead-lock.
              */
             final TypeNames c = new TypeNames(this);
             synchronized (this) {                       // Double-check strategy is ok if `typeNames` is volatile.
-                t = typeNames;
-                if (t == null) {
-                    typeNames = t = c;
+                mapper = typeNames;
+                if (mapper == null) {
+                    typeNames = mapper = c;
                 }
             }
         }
-        return t.toTypeName(this, valueClass);
+        return mapper.toTypeName(this, valueClass);
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultTypeName.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultTypeName.java
index deaca620e4..7e00cfc999 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultTypeName.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/DefaultTypeName.java
@@ -16,6 +16,9 @@
  */
 package org.apache.sis.util.iso;
 
+import java.util.Objects;
+import java.util.Optional;
+import java.lang.reflect.Type;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlRootElement;
 import org.opengis.util.TypeName;
@@ -33,7 +36,7 @@ import org.apache.sis.util.UnknownNameException;
  * </ul>
  *
  * <h2>Mapping Java classes to type names</h2>
- * It is sometime useful to establish a mapping between {@link Class} and {@code TypeName}.
+ * A bidirectional mapping is defined between {@code TypeName} and Java {@link Class}.
  * When an UML identifier from an OGC standard exists for a given {@code Class},
  * Apache SIS uses that identifier prefixed by the {@code "OGC"} namespace.
  * Note that this is <strong>not</strong> a standard practice.
@@ -114,7 +117,7 @@ import org.apache.sis.util.UnknownNameException;
  *
  * The mapping defined by Apache SIS may change in any future version depending on standardization progress.
  * To protect against such changes, users are encouraged to rely on methods or constructors like
- * {@link DefaultNameFactory#toTypeName(Class)} or {@link #toClass()} instead of parsing the name.
+ * {@link #toJavaType()} or {@link DefaultNameFactory#toTypeName(Class)} instead of parsing the name.
  *
  *
  * <h2>Immutability and thread safety</h2>
@@ -125,7 +128,7 @@ import org.apache.sis.util.UnknownNameException;
  * @author  Guilhem Legal (Geomatys)
  * @author  Cédric Briançon (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.5
+ * @version 1.3
  *
  * @see DefaultMemberName
  * @see DefaultNameFactory
@@ -139,33 +142,85 @@ public class DefaultTypeName extends DefaultLocalName implements TypeName {
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 7182126541436753582L;
+    private static final long serialVersionUID = 7571710679743017926L;
 
     /**
-     * The value class to be returned by {@link #toClass()}, or {@code null} if not yet computed.
-     * {@link Void#TYPE} is used as a sentinel value meaning explicit {@code null}.
+     * The value returned by {@link #toJavaType()}, or {@code null} if none.
+     * This is usually a {@link Class}, which is serializable.
+     */
+    @SuppressWarnings("serial")         // Not statically typed as Serializable.
+    private final Type javaType;
+
+    /**
+     * Constructs a type name from the given character sequence and infers automatically a Java type.
+     * The scope and name arguments are given unchanged to the
+     * {@linkplain DefaultLocalName#DefaultLocalName(NameSpace,CharSequence) super-class constructor}.
+     * Then the Java type is inferred in a way that depends on the specified scope:
+     *
+     * <ul>
+     *   <li>If the scope is {@code "OGC"}, then:
+     *     <ul>
+     *       <li>If the name is {@code "CharacterString"}, {@code "Integer"}, {@code "Real"} or other recognized names
+     *           (see {@linkplain DefaultTypeName class javadoc}),
+     *           then the corresponding Java class is associated to this type name.</li>
+     *       <li>Otherwise {@link UnknownNameException} is thrown.</li>
+     *     </ul>
+     *   </li>
+     *   <li>Else if the scope is {@code "class"}, then:
+     *     <ul>
+     *       <li>If the name is accepted by {@link Class#forName(String)},
+     *           then that Java class is associated to this type name.</li>
+     *       <li>Otherwise {@link UnknownNameException} is thrown.</li>
+     *     </ul>
+     *   </li>
+     *   <li>Else if the scope {@linkplain DefaultNameSpace#isGlobal() is global}, then:
+     *     <ul>
+     *       <li>If the name is one of the names recognized in {@code "OGC"} scope (see above),
+     *           then the corresponding class is associated to this type name.</li>
+     *       <li>Otherwise no Java class is associated to this type name.
+     *           No exception is thrown because names in the global namespace could be anything;
+     *           this constructor can not know if the given name was wrong.</li>
+     *     </ul>
+     *   </li>
+     *   <li>Otherwise no Java class is associated to this type name,
+     *       because this method can not check the validity of names in other namespaces.</li>
+     * </ul>
      *
-     * <p>This value is only computed. We do not allow the user to explicitly specify it, because we
-     * need that {@code DefaultTypeName}s having identical name also have the same {@code valueClass}.
-     * This is necessary {@link DefaultNameFactory#pool} cache integrity. Users who want to explicitly
-     * specify their own value class can override {@link #toClass()} instead.</p>
+     * @param  scope  the scope of this name, or {@code null} for a global scope.
+     * @param  name   the local name (never {@code null}).
+     * @throws UnknownNameException if a mapping from this name to a Java class was expected to exist
+     *         (because the specified scope is "OGC" or "class") but the associated Java class can not be found.
      *
-     * @see #setValueClass(NameSpace, String, Class)
-     * @see #toClass()
+     * @see DefaultNameFactory#createTypeName(NameSpace, CharSequence)
      */
-    private transient Class<?> valueClass;
+    protected DefaultTypeName(final NameSpace scope, final CharSequence name) throws UnknownNameException {
+        super(scope, name);
+        try {
+            javaType = TypeNames.toClass(TypeNames.namespace(scope), super.toString());
+        } catch (ClassNotFoundException e) {
+            throw new UnknownNameException(TypeNames.unknown(super.toFullyQualifiedName()), e);
+        }
+        if (javaType == Void.TYPE) {
+            throw new UnknownNameException(TypeNames.unknown(super.toFullyQualifiedName()));
+        }
+    }
 
     /**
-     * Constructs a type name from the given character sequence. The argument are given unchanged to the
+     * Constructs a type name from the given character sequence and explicit Java type.
+     * The scope and name arguments are given unchanged to the
      * {@linkplain DefaultLocalName#DefaultLocalName(NameSpace,CharSequence) super-class constructor}.
      *
-     * @param scope  the scope of this name, or {@code null} for a global scope.
-     * @param name   the local name (never {@code null}).
+     * @param scope     the scope of this name, or {@code null} for a global scope.
+     * @param name      the local name (never {@code null}).
+     * @param javaType  the value type to be returned by {@link #toJavaType()}, or {@code null} if none.
      *
-     * @see DefaultNameFactory#createTypeName(NameSpace, CharSequence)
+     * @see DefaultNameFactory#createTypeName(NameSpace, CharSequence, Type)
+     *
+     * @since 1.3
      */
-    protected DefaultTypeName(final NameSpace scope, final CharSequence name) {
+    protected DefaultTypeName(final NameSpace scope, final CharSequence name, final Type javaType) {
         super(scope, name);
+        this.javaType = javaType;
     }
 
     /**
@@ -190,88 +245,59 @@ public class DefaultTypeName extends DefaultLocalName implements TypeName {
         if (object == null || object instanceof DefaultTypeName) {
             return (DefaultTypeName) object;
         }
-        return new DefaultTypeName(object.scope(), object.toInternationalString());
+        return new DefaultTypeName(object.scope(), object.toInternationalString(), object.toJavaType().orElse(null));
     }
 
     /**
-     * Sets {@link #valueClass} to the given value, only if the scope and the name of this {@code TypeName}
-     * are equal to the given values. The check for scope and name is a protection against renaming that user
-     * could apply if they subclass {@link DefaultNameFactory}. If the user performed such renaming, then the
-     * value class may be wrong, so we will ignore the given value class and let {@link #toClass()} computes
-     * the class itself.
+     * Returns the Java type represented by this name.
+     * This is the type either specified explicitly at construction time or inferred from the type name.
+     *
+     * @return the Java type (usually a {@link Class}) for this type name.
+     *
+     * @see Names#toClass(TypeName)
+     *
+     * @since 1.3
      */
-    final void setValueClass(final NameSpace scope, final String name, final Class<?> valueClass) {
-        if (scope == super.scope() && name.equals(super.toString())) {
-            this.valueClass = valueClass;
-        }
+    @Override
+    public Optional<Type> toJavaType() {
+        return Optional.ofNullable(javaType);
     }
 
     /**
      * Returns the Java class associated to this type name.
-     * The default implementation parses this name in different ways depending on the {@linkplain #scope() scope}:
      *
-     * <ul>
-     *   <li>If the scope is {@code "OGC"}, then:
-     *     <ul>
-     *       <li>If the name is {@code "CharacterString"}, {@code "Integer"}, {@code "Real"} or other recognized names
-     *           (see {@linkplain DefaultTypeName class javadoc}), then the corresponding class is returned.</li>
-     *       <li>Otherwise {@link UnknownNameException} is thrown.</li>
-     *     </ul>
-     *   </li>
-     *   <li>Else if the scope is {@code "class"}, then:
-     *     <ul>
-     *       <li>If the name is accepted by {@link Class#forName(String)}, then that class is returned.</li>
-     *       <li>Otherwise {@link UnknownNameException} is thrown.</li>
-     *     </ul>
-     *   </li>
-     *   <li>Else if the scope {@linkplain DefaultNameSpace#isGlobal() is global}, then:
-     *     <ul>
-     *       <li>If the name is one of the names recognized in {@code "OGC"} scope (see above),
-     *           then the corresponding class is returned.</li>
-     *       <li>Otherwise {@code null} is returned. No exception is thrown because names in the global namespace
-     *           could be anything, so we can not be sure that the given name was wrong.</li>
-     *     </ul>
-     *   </li>
-     *   <li>Otherwise {@code null} is returned, since this method can not check the validity of names in other
-     *       namespaces.</li>
-     * </ul>
+     * @deprecated Replaced by {@link #toJavaType()}.
      *
      * @return the Java class associated to this {@code TypeName},
      *         or {@code null} if there is no mapping from this name to a Java class.
-     * @throws UnknownNameException if a mapping from this name to a Java class was expected to exist
-     *         (typically because of the {@linkplain #scope() scope}) but the operation failed.
-     *
-     * @see Names#toClass(TypeName)
-     * @see DefaultNameFactory#toTypeName(Class)
      *
      * @since 0.5
      */
-    public Class<?> toClass() throws UnknownNameException {
-        /*
-         * No synchronization, because it is not a problem if two threads compute the same value concurrently.
-         * No volatile field neither, because instances of Class are safely published (well, I presume...).
-         */
-        Class<?> c = valueClass;
-        if (c == Void.TYPE) {
-            return null;
-        }
-        if (c == null) {
-            /*
-             * Invoke super.foo() instead of this.foo() because we do not want to invoke any overridden method.
-             * This is for ensuring that two TypeNames constructed with the same name will map to the same class.
-             * See `valueClass` javadoc for more information.
-             */
-            try {
-                c = TypeNames.toClass(TypeNames.namespace(super.scope()), super.toString());
-            } catch (ClassNotFoundException e) {
-                throw new UnknownNameException(TypeNames.unknown(super.toFullyQualifiedName()), e);
-            }
-            if (c == null) {
-                throw new UnknownNameException(TypeNames.unknown(super.toFullyQualifiedName()));
-            }
-            valueClass = c;
+    @Deprecated
+    public Class<?> toClass() {
+        return (javaType instanceof Class<?>) ? (Class<?>) javaType : null;
+    }
+
+    /**
+     * Compares this type name with the specified object for equality.
+     *
+     * @param  object  the object to compare with this type for equality.
+     * @return {@code true} if the given object is equal to this name.
+     */
+    @Override
+    public boolean equals(final Object object) {
+        if (super.equals(object)) {
+            return Objects.equals(javaType, ((DefaultTypeName) object).javaType);
         }
-        return (c != Void.TYPE) ? c : null;
+        return false;
+    }
+
+    /**
+     * Invoked by {@link #hashCode()} for computing the hash code value when first needed.
+     */
+    @Override
+    int computeHashCode() {
+        return super.computeHashCode() ^ Objects.hashCode(javaType);
     }
 
 
@@ -293,5 +319,6 @@ public class DefaultTypeName extends DefaultLocalName implements TypeName {
      * the {@link #name} field will be set by JAXB during unmarshalling.
      */
     private DefaultTypeName() {
+        javaType = null;
     }
 }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Names.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Names.java
index cdce2bb011..8e80469e5a 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Names.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/Names.java
@@ -17,6 +17,7 @@
 package org.apache.sis.util.iso;
 
 import java.util.Collections;
+import java.lang.reflect.Type;
 import org.opengis.util.TypeName;
 import org.opengis.util.LocalName;
 import org.opengis.util.MemberName;
@@ -292,7 +293,7 @@ public final class Names extends Static {
         ensureNonNull("valueClass", valueClass);
         final DefaultNameFactory factory = DefaultFactories.forBuildin(NameFactory.class, DefaultNameFactory.class);
         return factory.createMemberName(createNameSpace(factory, namespace, separator), localPart,
-                factory.toTypeName(valueClass));    // SIS-specific method.
+               factory.toTypeName(valueClass));     // SIS-specific method.
     }
 
     /**
@@ -338,8 +339,7 @@ public final class Names extends Static {
      *
      * <ul>
      *   <li>If the given type name is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Else if the given type name is an instance of {@code DefaultTypeName},
-     *       then this method delegates to {@link DefaultTypeName#toClass()}.</li>
+     *   <li>Else if the value returned by {@link DefaultTypeName#toJavaType()} is a {@link Class}, returns that class.</li>
      *   <li>Else if the type name {@linkplain DefaultTypeName#scope() scope} is {@code "OGC"}, then:
      *     <ul>
      *       <li>If the name is {@code "CharacterString"}, {@code "Integer"}, {@code "Real"} or other recognized names
@@ -358,21 +358,21 @@ public final class Names extends Static {
      *       <li>If the name is one of the names recognized in {@code "OGC"} scope (see above),
      *           then the corresponding class is returned.</li>
      *       <li>Otherwise {@code null} is returned. No exception is thrown because names in the global namespace
-     *           could be anything, so we can not be sure that the given name was wrong.</li>
+     *           could be anything; this method can not be sure that the given name was wrong.</li>
      *     </ul>
      *   </li>
-     *   <li>Otherwise {@code null} is returned, since this method can not check the validity of names in other
-     *       namespaces.</li>
+     *   <li>Otherwise {@code null} is returned,
+     *       because this method can not check the validity of names in other namespaces.</li>
      * </ul>
      *
      * @param  type  the type name from which to infer a Java class.
      * @return the Java class associated to the given {@code TypeName},
      *         or {@code null} if there is no mapping from the given name to a Java class.
      * @throws UnknownNameException if a mapping from the given name to a Java class was expected to exist
-     *         (typically because of the {@linkplain DefaultTypeName#scope() scope}) but the operation failed.
+     *         (typically because of the {@linkplain DefaultTypeName#scope() scope}) but the lookup failed.
      *
      * @see #createTypeName(Class)
-     * @see DefaultTypeName#toClass()
+     * @see DefaultTypeName#toJavaType()
      *
      * @since 0.5
      */
@@ -380,21 +380,18 @@ public final class Names extends Static {
         if (type == null) {
             return null;
         }
-        Class<?> c;
-        if (type instanceof DefaultTypeName) {
-            c = ((DefaultTypeName) type).toClass();
-        } else {
-            try {
-                c = TypeNames.toClass(TypeNames.namespace(type.scope()), type.toString());
-            } catch (ClassNotFoundException e) {
-                throw new UnknownNameException(TypeNames.unknown(type), e);
-            }
-            if (c == null) {
-                throw new UnknownNameException(TypeNames.unknown(type));
-            }
-            if (c == Void.TYPE) {
-                c = null;
-            }
+        final Type t = type.toJavaType().orElse(null);
+        if (t instanceof Class<?>) {
+            return (Class<?>) t;
+        }
+        final Class<?> c;
+        try {
+            c = TypeNames.toClass(TypeNames.namespace(type.scope()), type.toString());
+        } catch (ClassNotFoundException e) {
+            throw new UnknownNameException(TypeNames.unknown(type), e);
+        }
+        if (c == Void.TYPE) {
+            throw new UnknownNameException(TypeNames.unknown(type));
         }
         return c;
     }
diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/TypeNames.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/TypeNames.java
index 2c28bad824..356d520f14 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/TypeNames.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/TypeNames.java
@@ -127,34 +127,29 @@ search: if (CharSequence.class.isAssignableFrom(valueClass)) {
                 name = valueClass.getName();    // See above comment.
             }
         }
-        /*
-         * Now create the name and remember the `valueClass` for that name if the implementation allows that.
-         */
-        final TypeName t = factory.createTypeName(ns, name);
-        if (t instanceof DefaultTypeName) {
-            ((DefaultTypeName) t).setValueClass(ns, name, valueClass);
+        if (factory instanceof DefaultNameFactory) {
+            return ((DefaultNameFactory) factory).createTypeName(ns, name, valueClass);
         }
-        return t;
+        return factory.createTypeName(ns, name);
     }
 
     /**
      * Returns the class for a {@code TypeName} made of the given scope and name.
      * This method is the converse of {@link #toTypeName(NameFactory, Class)}.
-     * This method returns 3 kind of values:
+     * There is 3 kinds of return value:
      *
      * <ul>
-     *   <li>{@code Void.TYPE} if the namespace or the name is unrecognized, without considering that as an error.
-     *       This is a sentinel value expected by {@link DefaultTypeName#toClass()} for such case.</li>
-     *   <li>{@code null} if {@code namespace} is recognized, but not the {@code name}.
-     *       This will be considered as an error by {@link DefaultTypeName#toClass()}.</li>
+     *   <li>{@code null} if the namespace or the name is unrecognized, without considering that as an error.</li>
+     *   <li>{@code Void.TYPE} if {@code namespace} is recognized, but not the {@code name}.
+     *       This is a sentinel value to be considered as an error by {@link DefaultTypeName} constructor.</li>
      *   <li>Otherwise the class for the given name.</li>
      * </ul>
      *
      * @param  namespace  the namespace, case-insensitive. Can be any value, but this method recognizes
      *         only {@code "OGC"}, {@code "class"} and {@code null}. Other namespaces will be ignored.
-     * @param  name  the name, case-sensitive.
-     * @return the class, or {@code Void.TYPE} if the given namespace is not recognized,
-     *         or {@code null} if the namespace is recognized but not the name.
+     * @param  name  the type name, case-sensitive.
+     * @return the class, or {@code null} if the given namespace is not recognized,
+     *         or {@code Void.TYPE} if the namespace is recognized but not the type name.
      * @throws ClassNotFoundException if {@code namespace} is {@code "class"} but {@code name} is not
      *         the name of a reachable class.
      */
@@ -164,20 +159,20 @@ search: if (CharSequence.class.isAssignableFrom(valueClass)) {
             c = MAPPING.get(name);
             if (c == null) {
                 c = Types.forStandardName(name);
-                if (c == null && namespace == null) {
-                    c = Void.TYPE;          // Unknown name not considered an error if not in "OGC" namespace.
+                if (c == null && namespace != null) {
+                    c = Void.TYPE;          // Unknown name in OGC namespace.
                 }
             }
         } else if (namespace.equalsIgnoreCase("class")) {
             c = Class.forName(name);
         } else {
-            c = Void.TYPE;                  // Not an "OGC" or "class" namespace.
+            c = null;                   // Not an "OGC" or "class" namespace.
         }
         return c;
     }
 
     /**
-     * Ensures that the given class is not {@link Void#TYPE}.
+     * Ensures that the given class is non-null and not {@link Void#TYPE}.
      * This is a helper method for callers of {@link #toTypeName(NameFactory, Class)}.
      */
     static boolean isValid(final Class<?> valueClass) {
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/util/iso/NamesTest.java b/core/sis-metadata/src/test/java/org/apache/sis/util/iso/NamesTest.java
index 454f71e0b2..2463f844de 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/util/iso/NamesTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/util/iso/NamesTest.java
@@ -39,7 +39,7 @@ import static org.junit.Assert.*;
  * Tests the {@link Names} class.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.3
  * @since   0.5
  * @module
  */
@@ -84,8 +84,12 @@ public final strictfp class NamesTest extends TestCase {
         assertValueClassEquals(Random.class, type);
         assertValueClassEquals(DefaultNameFactoryTest.class,
                 new DefaultTypeName(type.scope(), DefaultNameFactoryTest.class.getName()));
-        assertValueClassEquals(UnknownNameException.class,
-                new DefaultTypeName(type.scope(), "org.apache.sis.Dummy"));
+        try {
+            new DefaultTypeName(type.scope(), "org.apache.sis.Dummy");
+            fail("Expected UnknownNameException.");
+        } catch (UnknownNameException e) {
+            assertTrue(e.getMessage().contains("org.apache.sis.Dummy"));
+        }
     }
 
     /**
@@ -100,7 +104,12 @@ public final strictfp class NamesTest extends TestCase {
         assertValueClassEquals(String.class,               type);
         assertValueClassEquals(Double.class,               new DefaultTypeName(type.scope(), "Real"));
         assertValueClassEquals(InternationalString.class,  new DefaultTypeName(type.scope(), "FreeText"));
-        assertValueClassEquals(UnknownNameException.class, new DefaultTypeName(type.scope(), "Dummy"));
+        try {
+            new DefaultTypeName(type.scope(), "Dummy");
+            fail("Expected UnknownNameException.");
+        } catch (UnknownNameException e) {
+            assertTrue(e.getMessage().contains("OGC:Dummy"));
+        }
     }
 
     /**
@@ -116,29 +125,15 @@ public final strictfp class NamesTest extends TestCase {
         assertValueClassEquals(null,         Names.createTypeName(null,   null, "Dummy"));
     }
 
-    /**
-     * Invokes {@link Names#toClass(TypeName)}, but catch {@link UnknownNameException}.
-     * If the latter exception is caught, then this method returns {@code UnknownNameException.class}.
-     */
-    private static Class<?> toClass(final TypeName type) {
-        try {
-            return Names.toClass(type);
-        } catch (UnknownNameException e) {
-            final String message = e.getMessage();
-            assertTrue(message, message.contains(type.toFullyQualifiedName().toString()));
-            return UnknownNameException.class;
-        }
-    }
-
     /**
      * Asserts that calls to {@link Names#toClass(TypeName)} returns the expected value class.
      */
     private static void assertValueClassEquals(final Class<?> expected, final TypeName type) {
-        assertEquals(expected, toClass(type));
+        assertEquals(expected, Names.toClass(type));
         /*
          * Tests detection with an implementation which is not the SIS one.
          */
-        assertEquals(expected, toClass(new TypeName() {
+        assertEquals(expected, Names.toClass(new TypeName() {
             @Override public int                       depth()                  {return type.depth();}
             @Override public List<? extends LocalName> getParsedNames()         {return type.getParsedNames();}
             @Override public LocalName                 head()                   {return type.head();}
diff --git a/core/sis-metadata/src/test/java/org/apache/sis/util/iso/TypeNamesTest.java b/core/sis-metadata/src/test/java/org/apache/sis/util/iso/TypeNamesTest.java
index c217af0338..c15347b357 100644
--- a/core/sis-metadata/src/test/java/org/apache/sis/util/iso/TypeNamesTest.java
+++ b/core/sis-metadata/src/test/java/org/apache/sis/util/iso/TypeNamesTest.java
@@ -38,7 +38,7 @@ import static org.apache.sis.internal.util.Constants.OGC;
  * Tests are performed through the {@link DefaultNameFactory#toTypeName(Class)} method.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.5
+ * @version 1.3
  * @since   0.5
  * @module
  */
@@ -57,7 +57,7 @@ public final strictfp class TypeNamesTest extends TestCase {
         final DefaultNameFactory factory = DefaultFactories.forBuildin(NameFactory.class, DefaultNameFactory.class);
         final TypeName type = factory.toTypeName(valueClass);
         assertNotNull(name, type);
-        assertSame   (name, valueClass, ((DefaultTypeName) type).toClass());
+        assertSame   (name, valueClass, type.toJavaType().get());
         assertEquals (name, namespace,  type.scope().name().toString());
         assertEquals (name, name,       type.toString());
         assertEquals (name, valueClass, TypeNames.toClass(namespace, name));
@@ -127,10 +127,10 @@ public final strictfp class TypeNamesTest extends TestCase {
      */
     @Test
     public void testInvalidNames() throws ClassNotFoundException {
-        assertEquals("Dummy:Real", Void.TYPE,    TypeNames.toClass("Dummy", "Real"));
+        assertNull  ("Dummy:Real",               TypeNames.toClass("Dummy", "Real"));
         assertEquals(OGC+":Real",  Double.class, TypeNames.toClass(OGC,     "Real"));
         assertEquals("Real",       Double.class, TypeNames.toClass(null,    "Real"));
-        assertEquals("Dummy",      Void.TYPE,    TypeNames.toClass(null,    "Dummy")); // Considered not an error.
-        assertNull  (OGC+":Dummy",               TypeNames.toClass(OGC,     "Dummy")); // Considered an error.
+        assertNull  ("Dummy",                    TypeNames.toClass(null,    "Dummy"));    // Considered not an error.
+        assertEquals(OGC+":Dummy", Void.TYPE,    TypeNames.toClass(OGC,     "Dummy"));    // Considered an error.
     }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/UnknownNameException.java b/core/sis-utility/src/main/java/org/apache/sis/util/UnknownNameException.java
index 824a817fa8..6ce8c284ab 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/UnknownNameException.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/UnknownNameException.java
@@ -24,14 +24,15 @@ package org.apache.sis.util;
  * or any other objects with similar purpose.
  *
  * <p><b>Note:</b> in the particular case of objects created from a {@link org.opengis.util.Factory},
- * the exception for unrecognized identifiers is rather {@link org.opengis.util.NoSuchIdentifierException}.</p>
+ * the exception for unrecognized identifiers is rather {@link org.opengis.util.NoSuchIdentifierException}.
+ * This {@code UnknownNameException} differs in being an unchecked exception.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.5
+ * @version 1.3
  * @since   0.5
  * @module
  */
-public class UnknownNameException extends RuntimeException {
+public class UnknownNameException extends IllegalArgumentException {
     /**
      * For cross-version compatibility.
      */


[sis] 01/02: Generalize `Numbers.isNumber(Class)` by checking also for types unknown to this static method.

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit f06c086e842fa9f13b9deeea0caa24a08489fab4
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Sun Nov 13 15:34:35 2022 +0100

    Generalize `Numbers.isNumber(Class)` by checking also for types unknown to this static method.
---
 .../src/main/java/org/apache/sis/util/iso/TypeNames.java         | 2 +-
 core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java  | 9 +++++----
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/TypeNames.java b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/TypeNames.java
index a4da980c77..2c28bad824 100644
--- a/core/sis-metadata/src/main/java/org/apache/sis/util/iso/TypeNames.java
+++ b/core/sis-metadata/src/main/java/org/apache/sis/util/iso/TypeNames.java
@@ -99,7 +99,7 @@ final class TypeNames {
         NameSpace ns = ogcNS;
 search: if (CharSequence.class.isAssignableFrom(valueClass)) {
             name = InternationalString.class.isAssignableFrom(valueClass) ? "FreeText" : "CharacterString";
-        } else if (Numbers.isNumber(valueClass) || Number.class.isAssignableFrom(valueClass)) {
+        } else if (Numbers.isNumber(valueClass)) {
             name = Numbers.isInteger(valueClass) ? "Integer" : "Real";
         } else {
             /*
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java b/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java
index 2f60997bf0..c0e337cb99 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java
@@ -39,7 +39,7 @@ import static java.lang.Double.doubleToLongBits;
  * Static methods working with {@link Number} objects, and a few primitive types by extension.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.1
+ * @version 1.3
  *
  * @see org.apache.sis.math.MathFunctions
  *
@@ -183,10 +183,10 @@ public final class Numbers extends Static {
     /**
      * Returns {@code true} if the given {@code type} is a floating point or an integer type.
      * This method returns {@code true} if either {@link #isFloat(Class)} or {@link #isInteger(Class)}
-     * returns {@code true} for the given argument.
+     * returns {@code true} for the given argument, or if the type is assignable to {@link Number}.
      *
      * @param  type  the primitive type or wrapper class to test (can be {@code null}).
-     * @return {@code true} if {@code type} is a floating point or an integer type.
+     * @return {@code true} if {@code type} is a {@link Number} or a primitive floating point or integer type.
      *
      * @see #isFloat(Class)
      * @see #isInteger(Class)
@@ -195,7 +195,8 @@ public final class Numbers extends Static {
      */
     public static boolean isNumber(final Class<?> type) {
         final Numbers mapping = MAPPING.get(type);
-        return (mapping != null) && (mapping.isInteger | mapping.isFloat);
+        return ((mapping != null) && (mapping.isInteger | mapping.isFloat)) ||
+               ((type != null) && Number.class.isAssignableFrom(type));
     }
 
     /**