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. */