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 <martin.desruisse...@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.
      */


Reply via email to