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

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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 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.
4f2555bbe9 is described below

commit 4f2555bbe92561145f254f243ad1a6d526c22d87
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Sat Nov 12 21:14:51 2022 +0100

    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.
---
 .../org/apache/sis/feature/AbstractAttribute.java  |   3 +-
 .../apache/sis/feature/DefaultAttributeType.java   |   3 +-
 .../java/org/apache/sis/util/iso/TypeNames.java    |   8 +-
 .../sis/parameter/DefaultParameterDescriptor.java  |  18 +-
 .../parameter/DefaultParameterDescriptorTest.java  |  50 +++---
 .../src/main/java/org/apache/sis/util/Classes.java | 190 +++++++++++++++------
 .../test/java/org/apache/sis/util/ClassesTest.java |  86 +++++++---
 7 files changed, 251 insertions(+), 107 deletions(-)

diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java 
b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
index e897bb4829..ea8ec1c0ab 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/feature/AbstractAttribute.java
@@ -69,7 +69,8 @@ import org.opengis.feature.MultiValuedPropertyException;
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8
  *
- * @param <V>  the type of attribute values.
+ * @param <V> the type of attribute values. If the attribute supports 
multi-occurrences,
+ *            then this is the type of elements (not the collection type).
  *
  * @see AbstractFeature
  * @see DefaultAttributeType
diff --git 
a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
 
b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
index 2c1fc7a681..65788e7c41 100644
--- 
a/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
+++ 
b/core/sis-feature/src/main/java/org/apache/sis/feature/DefaultAttributeType.java
@@ -97,7 +97,8 @@ import org.opengis.feature.AttributeType;
  * @author  Martin Desruisseaux (Geomatys)
  * @version 0.8
  *
- * @param <V>  the type of attribute values.
+ * @param <V> the type of attribute values. If the attribute supports 
multi-occurrences,
+ *            then this is the type of elements (not the collection type).
  *
  * @see DefaultFeatureType
  * @see AbstractAttribute
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 48325ec7d5..a4da980c77 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
@@ -37,7 +37,7 @@ import org.apache.sis.util.Numbers;
  * Implements the mapping between {@link Class} and {@link TypeName} 
documented in {@link DefaultTypeName}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.3
  * @since   0.5
  * @module
  */
@@ -97,9 +97,9 @@ final class TypeNames {
     final TypeName toTypeName(final NameFactory factory, final Class<?> 
valueClass) {
         String name;
         NameSpace ns = ogcNS;
-        if (CharSequence.class.isAssignableFrom(valueClass)) {
+search: if (CharSequence.class.isAssignableFrom(valueClass)) {
             name = InternationalString.class.isAssignableFrom(valueClass) ? 
"FreeText" : "CharacterString";
-        } else if (Number.class.isAssignableFrom(valueClass)) {
+        } else if (Numbers.isNumber(valueClass) || 
Number.class.isAssignableFrom(valueClass)) {
             name = Numbers.isInteger(valueClass) ? "Integer" : "Real";
         } else {
             /*
@@ -113,7 +113,7 @@ final class TypeNames {
                 base = entry.getValue();
                 if (base.isAssignableFrom(valueClass)) {
                     name = entry.getKey();
-                    return factory.createTypeName(ns, name);
+                    break search;
                 }
             } while (base != Boolean.class);    // See MAPPING javadoc for the 
role of Boolean as a sentinel value.
             /*
diff --git 
a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java
 
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java
index e73d118369..16cc89e3a7 100644
--- 
a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java
+++ 
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java
@@ -316,14 +316,28 @@ public class DefaultParameterDescriptor<T> extends 
AbstractParameterDescriptor i
 
     /**
      * Returns the name that describes the type of parameter values.
+     * This is closely related to the {@link Class} returned by {@link 
#getValueClass()}:
      *
-     * @return value type of the parameter.
+     * <ul>
+     *   <li>If the value class is a collection ({@link java.util.Map}, {@link 
Set}, {@link java.util.List} or array),
+     *       then this method returns the type of <em>elements</em> in the 
collection.</li>
+     *   <li>Otherwise this method returns the value class using the mapping 
documented in
+     *       {@link org.apache.sis.util.iso.DefaultTypeName} javadoc.</li>
+     * </ul>
+     *
+     * @return the type name of value component(s) in this parameter.
      *
      * @since 1.3
      */
     @Override
     public final TypeName getValueType() {
-        return Names.createTypeName(valueClass);
+        Class<?> type = valueClass;
+        if (Iterable.class.isAssignableFrom(type) || 
Map.class.isAssignableFrom(type)) {
+            type = Classes.boundOfParameterizedDeclaration(valueClass);
+        } else if (type.isArray()) {
+            type = type.getComponentType();
+        }
+        return Names.createTypeName(type);
     }
 
     /**
diff --git 
a/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorTest.java
 
b/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorTest.java
index 6aef74bef8..3667a0ff5b 100644
--- 
a/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorTest.java
+++ 
b/core/sis-referencing/src/test/java/org/apache/sis/parameter/DefaultParameterDescriptorTest.java
@@ -42,7 +42,7 @@ import static org.apache.sis.test.ReferencingAssert.*;
  * Tests the {@link DefaultParameterDescriptor} class.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.8
+ * @version 1.3
  * @since   0.4
  * @module
  */
@@ -159,6 +159,7 @@ public final strictfp class DefaultParameterDescriptorTest 
extends TestCase {
     public void testOptionalInteger() {
         final ParameterDescriptor<Integer> descriptor = 
createSimpleOptional("Simple param", Integer.class);
         assertEquals("name",      "Simple param", 
descriptor.getName().getCode());
+        assertEquals("valueType",  "Integer",     
descriptor.getValueType().toString());
         assertEquals("valueClass", Integer.class, descriptor.getValueClass());
         assertNull  ("validValues",               descriptor.getValidValues());
         assertNull  ("defaultValue",              
descriptor.getDefaultValue());
@@ -184,6 +185,7 @@ public final strictfp class DefaultParameterDescriptorTest 
extends TestCase {
         }
         final ParameterDescriptor<Integer> descriptor = create("Test range", 
4, 20, 12);
         assertEquals("name",          "Test range",        
descriptor.getName().getCode());
+        assertEquals("valueType",     "Integer",           
descriptor.getValueType().toString());
         assertEquals("valueClass",    Integer.class,       
descriptor.getValueClass());
         assertNull  ("validValues",                        
descriptor.getValidValues());
         assertEquals("defaultValue",  Integer.valueOf(12), 
descriptor.getDefaultValue());
@@ -215,11 +217,12 @@ public final strictfp class 
DefaultParameterDescriptorTest extends TestCase {
     public void testDoubleType() {
         final ParameterDescriptor<Double> descriptor = create("Length 
measure", 4, 20, 12, Units.METRE);
         assertEquals("name",         "Length measure",   
descriptor.getName().getCode());
-        assertEquals("unit",         Units.METRE,           
descriptor.getUnit());
+        assertEquals("valueType",    "Real",             
descriptor.getValueType().toString());
         assertEquals("class",        Double.class,       
descriptor.getValueClass());
         assertEquals("defaultValue", Double.valueOf(12), 
descriptor.getDefaultValue());
         assertEquals("minimum",      Double.valueOf( 4), 
descriptor.getMinimumValue());
         assertEquals("maximum",      Double.valueOf(20), 
descriptor.getMaximumValue());
+        assertEquals("unit",         Units.METRE,        descriptor.getUnit());
         validate(descriptor);
     }
 
@@ -231,16 +234,17 @@ public final strictfp class 
DefaultParameterDescriptorTest extends TestCase {
         final Range<String> valueDomain = new Range<>(String.class, "AAA", 
true, "BBB", true);
         final DefaultParameterDescriptor<String> descriptor = new 
DefaultParameterDescriptor<>(
                 properties("String param"), 0, 1, String.class, valueDomain, 
null, "ABC");
-        assertEquals("name", "String param",     
descriptor.getName().getCode());
-        assertEquals("valueClass", String.class, descriptor.getValueClass());
-        assertNull  ("validValues",              descriptor.getValidValues());
-        assertSame  ("valueDomain", valueDomain, descriptor.getValueDomain());
-        assertEquals("defaultValue",  "ABC",     descriptor.getDefaultValue());
-        assertEquals("minimumValue",  "AAA",     descriptor.getMinimumValue());
-        assertEquals("maximumValue",  "BBB",     descriptor.getMaximumValue());
-        assertEquals("minimumOccurs", 0,         
descriptor.getMinimumOccurs());
-        assertEquals("maximumOccurs", 1,         
descriptor.getMaximumOccurs());
-        assertNull  ("unit",                     descriptor.getUnit());
+        assertEquals("name", "String param",         
descriptor.getName().getCode());
+        assertEquals("valueType", "CharacterString", 
descriptor.getValueType().toString());
+        assertEquals("valueClass", String.class,     
descriptor.getValueClass());
+        assertNull  ("validValues",                  
descriptor.getValidValues());
+        assertSame  ("valueDomain",   valueDomain,   
descriptor.getValueDomain());
+        assertEquals("defaultValue",  "ABC",         
descriptor.getDefaultValue());
+        assertEquals("minimumValue",  "AAA",         
descriptor.getMinimumValue());
+        assertEquals("maximumValue",  "BBB",         
descriptor.getMaximumValue());
+        assertEquals("minimumOccurs", 0,             
descriptor.getMinimumOccurs());
+        assertEquals("maximumOccurs", 1,             
descriptor.getMaximumOccurs());
+        assertNull  ("unit",                         descriptor.getUnit());
     }
 
     /**
@@ -252,15 +256,16 @@ public final strictfp class 
DefaultParameterDescriptorTest extends TestCase {
         final String[] enumeration = {"Apple", "Orange", "りんご"};
         final ParameterDescriptor<String> descriptor = create(
                 "Enumeration param", String.class, enumeration, "Apple");
-        assertEquals     ("name", "Enumeration param", 
descriptor.getName().getCode());
-        assertEquals     ("valueClass", String.class,  
descriptor.getValueClass());
-        assertArrayEquals("validValues", enumeration,  
descriptor.getValidValues().toArray());
-        assertEquals     ("defaultValue",  "Apple",    
descriptor.getDefaultValue());
-        assertNull       ("minimumValue",              
descriptor.getMinimumValue());
-        assertNull       ("maximumValue",              
descriptor.getMaximumValue());
-        assertEquals     ("minimumOccurs", 1,          
descriptor.getMinimumOccurs());
-        assertEquals     ("maximumOccurs", 1,          
descriptor.getMaximumOccurs());
-        assertNull       ("unit",                      descriptor.getUnit());
+        assertEquals     ("name", "Enumeration param",    
descriptor.getName().getCode());
+        assertEquals     ("valueType", "CharacterString", 
descriptor.getValueType().toString());
+        assertEquals     ("valueClass", String.class,     
descriptor.getValueClass());
+        assertArrayEquals("validValues", enumeration,     
descriptor.getValidValues().toArray());
+        assertEquals     ("defaultValue", "Apple",        
descriptor.getDefaultValue());
+        assertNull       ("minimumValue",                 
descriptor.getMinimumValue());
+        assertNull       ("maximumValue",                 
descriptor.getMaximumValue());
+        assertEquals     ("minimumOccurs", 1,             
descriptor.getMinimumOccurs());
+        assertEquals     ("maximumOccurs", 1,             
descriptor.getMaximumOccurs());
+        assertNull       ("unit",                         
descriptor.getUnit());
         /*
          * Invalid operation: element not in the list of valid elements.
          */
@@ -281,8 +286,9 @@ public final strictfp class DefaultParameterDescriptorTest 
extends TestCase {
     public void testArrayType() {
         final DefaultParameterDescriptor<double[]> descriptor = 
createForArray("Array param", 4, 9, Units.METRE);
         assertEquals("name",       "Array param",  
descriptor.getName().getCode());
+        assertEquals("valueType",  "Real",         
descriptor.getValueType().toString());
         assertEquals("valueClass", double[].class, descriptor.getValueClass());
-        assertEquals("unit",       Units.METRE,       descriptor.getUnit());
+        assertEquals("unit",       Units.METRE,    descriptor.getUnit());
         assertNull  ("validValues",                
descriptor.getValidValues());
         assertNull  ("defaultValue",               
descriptor.getDefaultValue());
         assertNull  ("minimumValue",               
descriptor.getMinimumValue());
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/Classes.java 
b/core/sis-utility/src/main/java/org/apache/sis/util/Classes.java
index fb676eebdc..66dcb2ac9a 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/Classes.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/Classes.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.util;
 
+import java.util.Map;
 import java.util.Set;
 import java.util.Arrays;
 import java.util.Iterator;
@@ -25,8 +26,10 @@ import java.util.LinkedHashSet;
 import java.lang.reflect.Type;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.lang.reflect.TypeVariable;
 import java.lang.reflect.WildcardType;
 import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.GenericDeclaration;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Modifier;
 import org.opengis.annotation.UML;
@@ -53,7 +56,7 @@ import static 
org.apache.sis.internal.system.Modules.INTERNAL_CLASSNAME_PREFIX;
  * </ul>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.1
+ * @version 1.3
  * @since   0.3
  * @module
  */
@@ -140,23 +143,30 @@ public final class Classes extends Static {
     /**
      * Returns the upper bounds of the parameterized type of the given 
property.
      * If the property does not have a parameterized type, returns {@code 
null}.
-     *
-     * <p>This method is typically used for fetching the type of elements in a 
collection.
-     * We do not provide a method working from a {@link Class} instance 
because of the way
-     * parameterized types are implemented in Java (by erasure).</p>
-     *
-     * <b>Examples:</b> When invoking this method for a field of the type 
below:
+     * If the property has more than one parameterized type, then the parameter
+     * examined by this method depends on the property type:
      * <ul>
-     *   <li>{@code Set<Number>} returns {@code Number.class}.</li>
-     *
-     *   <li>{@code Set<? extends Number>} returns {@code Number.class} as 
well, since that
-     *       collection can not (in theory) contain instances of 
super-classes; {@code Number}
-     *       is the <cite>upper bound</cite>.</li>
+     *   <li>If {@link Map}, then this method returns the type of keys in map 
entries.</li>
+     *   <li>For all other types, this method expects exactly one 
parameterized type
+     *       for avoiding ambiguity. If this is not the case, {@code null} is 
returned.</li>
+     * </ul>
      *
-     *   <li>{@code Set<? super Number>} returns {@code Object.class}, because 
that collection
-     *       is allowed to contain such elements.</li>
+     * This method is used for fetching the type of elements in a collection.
+     * This information can not be obtained from a {@link Class} instance
+     * because of the way parameterized types are implemented in Java (by 
erasure).
      *
-     *   <li>{@code Set} returns {@code null} because that collection is 
un-parameterized.</li>
+     * <h4>Examples</h4>
+     * When invoking this method for a field of the following types:
+     * <ul>
+     *   <li>{@code Map<String,Number>}: returns {@code String.class}, the 
type of keys.</li>
+     *   <li>{@code Set<Number>}: returns {@code Number.class}.</li>
+     *   <li>{@code Set<? extends Number>}: returns {@code Number.class} as 
well,
+     *       because that collection can not contain instances of 
super-classes.
+     *       {@code Number} is the <cite>upper bound</cite>.</li>
+     *   <li>{@code Set<? super Number>}: returns {@code Object.class},
+     *       because that collection is allowed to contain such elements.</li>
+     *   <li>{@code Set}: returns {@code null} because that collection is 
declared with raw type.</li>
+     *   <li>{@code Long}: returns {@code null} because that type is not 
parameterized.</li>
      * </ul>
      *
      * @param  field  the field for which to obtain the parameterized type.
@@ -168,70 +178,148 @@ public final class Classes extends Static {
     }
 
     /**
-     * If the given method is a getter or a setter for a parameterized 
property, returns the
-     * upper bounds of the parameterized type. Otherwise returns {@code null}. 
This method
-     * provides the same semantic than {@link 
#boundOfParameterizedProperty(Field)}, but
-     * works on a getter or setter method rather then the field. See the 
javadoc of above
-     * method for more details.
+     * If the given method is a getter or a setter for a parameterized 
property,
+     * returns the upper bounds of the parameterized type.
+     * Otherwise returns {@code null}.
+     * This method provides the same semantic than {@link 
#boundOfParameterizedProperty(Field)},
+     * but works on a getter or setter method rather than a field.
+     * See {@link #boundOfParameterizedProperty(Field)} javadoc for details.
      *
-     * <p>This method is typically used for fetching the type of elements in a 
collection.
-     * We do not provide a method working from a {@link Class} instance 
because of the way
-     * parameterized types are implemented in Java (by erasure).</p>
+     * <p>This method is used for fetching the type of elements in a 
collection.
+     * This information can not be obtained from a {@link Class} instance
+     * because of the way parameterized types are implemented in Java (by 
erasure).</p>
      *
      * @param  method  the getter or setter method for which to obtain the 
parameterized type.
      * @return the upper bound of parameterized type, or {@code null} if the 
given method
-     *         does not operate on an object of a parameterized type.
+     *         is not a getter or setter for a property of a parameterized 
type.
      */
     public static Class<?> boundOfParameterizedProperty(final Method method) {
-        Class<?> c = getActualTypeArgument(method.getGenericReturnType());
-        if (c == null) {
-            final Type[] parameters = method.getGenericParameterTypes();
-            if (parameters != null && parameters.length == 1) {
-                c = getActualTypeArgument(parameters[0]);
+        final Type[] parameters = method.getGenericParameterTypes();
+        final Type type;
+        switch (parameters.length) {
+            case 0:  type = method.getGenericReturnType(); break;
+            case 1:  type = parameters[0]; break;
+            default: return null;
+        }
+        return getActualTypeArgument(type);
+    }
+
+    /**
+     * Returns a single bound declared in a parameterized class or a 
parameterized method.
+     * The {@code typeOrMethod} argument is usually a {@link Class} for a 
collection type.
+     * If the given argument is a non-parameterized class, then this method 
searches for
+     * the first parameterized super-class (see example below).
+     * If no parameterized declaration is found, then this method returns 
{@code null}.
+     * If the declaration has more than one parameterized type, then this 
method applies
+     * the same heuristic rule as {@link #boundOfParameterizedProperty(Field)}
+     * (see the javadoc of that method for details).
+     *
+     * <h4>Examples</h4>
+     * When invoking this method with the following {@link Class} argument 
values:
+     * <ul>
+     *   <li>{@code List.class}: returns {@code Object.class} because {@link 
java.util.List} is declared as
+     *       {@code List<E>} (implicitly {@code <E extends Object>}).</li>
+     *   <li>{@code Map.class}: returns {@code Object.class} because {@link 
java.util.Map} is declared as
+     *       {@code Map<K,V>} and, as an heuristic rule, we return the key 
type of map entry.</li>
+     *   <li>{@code PrinterStateReasons.class}: returns {@code 
PrinterStateReason.class} because
+     *       {@link javax.print.attribute.standard.PrinterStateReasons} is not 
parameterized but extends
+     *       {@code HashMap<PrinterStateReason,Severity>}.</li>
+     *   <li>{@code Long.class}: returns {@code null} because that type is not 
parameterized.</li>
+     * </ul>
+     *
+     * This method is used as a fallback when {@code 
boundOfParameterizedProperty(…)} can not be used.
+     *
+     * @param  typeOrMethod  the {@link Class} or {@link Method} from which to 
get the bounds of its parameter.
+     * @return the upper bound of parameterized class or method, or {@code 
null} if this method can not identify
+     *         a single parameterized type to return.
+     *
+     * @see #boundOfParameterizedProperty(Field)
+     * @see #boundOfParameterizedProperty(Method)
+     *
+     * @since 1.3
+     */
+    public static Class<?> boundOfParameterizedDeclaration(final 
GenericDeclaration typeOrMethod) {
+        final TypeVariable<?>[] parameters = typeOrMethod.getTypeParameters();
+        final int i = chooseSingleType(typeOrMethod, parameters.length);
+        if (i >= 0) {
+            Class<?> bounds = null;
+            for (final Type p : parameters[i].getBounds()) {
+                if (p instanceof Class<?>) {
+                    bounds = findCommonClass(bounds, (Class<?>) p);
+                }
             }
+            return bounds;
         }
-        return c;
+        return getActualTypeArgument(typeOrMethod);
     }
 
     /**
-     * Delegates to {@link ParameterizedType#getActualTypeArguments()} and 
returns the result as a
-     * {@link Class}, provided that every objects are of the expected classes 
and the result was
-     * an array of length 1 (so there is no ambiguity). Otherwise returns 
{@code null}.
+     * Returns the type argument of the given type or the first parameterized 
parent type.
+     * For example if the given type is {@code List<String>}, then this method 
returns {@code String.class}.
+     * This method expects a fixed amount of parameterized types (currently 2 
if the given type is {@code Map}
+     * and 1 for all other types), otherwise it returns {@code null}.
+     *
+     * @see ParameterizedType#getActualTypeArguments()
      */
-    private static Class<?> getActualTypeArgument(Type type) {
-        if (type instanceof ParameterizedType) {
-            Type[] p = ((ParameterizedType) type).getActualTypeArguments();
-            while (p != null && p.length == 1) {
-                type = p[0];
-                if (type instanceof WildcardType) {
-                    p = ((WildcardType) type).getUpperBounds();
-                    continue;
+    private static Class<?> getActualTypeArgument(Object typeOrMethod) {
+        while (typeOrMethod instanceof Class<?>) {
+            typeOrMethod = ((Class<?>) typeOrMethod).getGenericSuperclass();
+        }
+        if (typeOrMethod instanceof ParameterizedType) {
+            final ParameterizedType p = (ParameterizedType) typeOrMethod;
+            final Type[] parameters = p.getActualTypeArguments();
+            final int i = chooseSingleType(p.getRawType(), parameters.length);
+            if (i >= 0) {
+                Type type = parameters[i];
+                while (type instanceof WildcardType) {
+                    final Type[] bounds = ((WildcardType) 
type).getUpperBounds();
+                    if (bounds.length != 1) return null;
+                    type = bounds[0];
                 }
                 /*
-                 * At this point we are not going to continue the loop anymore.
-                 * Check if we have an array, then check the (component) class.
+                 * If we have an array, unroll it until we get the class of 
array components.
+                 * The array will be reconstructed at the end of this method, 
but as a class
+                 * instead of as a generic type (i.e. we convert Type[][]… to 
Class[][]…).
                  */
-                if (type instanceof ParameterizedType) {
-                    /*
-                     * Example: replace ParameterDescriptor<?> by 
ParameterDescriptor
-                     * before we test if (type instanceof Class<?>).
-                     */
-                    type = ((ParameterizedType) type).getRawType();
-                }
                 int dimension = 0;
                 while (type instanceof GenericArrayType) {
                     type = ((GenericArrayType) type).getGenericComponentType();
                     dimension++;
                 }
+                /*
+                 * Then replace (for example) `ParameterDescriptor<?>` by 
`ParameterDescriptor`
+                 * in order to get an instance of `Class` instead of other 
kind of `Type`.
+                 */
+                if (type instanceof ParameterizedType) {
+                    type = ((ParameterizedType) type).getRawType();
+                }
                 if (type instanceof Class<?>) {
                     return changeArrayDimension((Class<?>) type, dimension);
                 }
-                break;                                      // Unknown type.
             }
         }
         return null;
     }
 
+    /**
+     * Chooses (using heuristic rules) a single element in an array of type 
arguments.
+     * The given raw type should be a {@link Class} instance when possible, 
usually a collection type.
+     * The given count shall be the number of parameters, for example 2 in 
{@code Map<String,Integer>}.
+     *
+     * @param  rawType  the parameterized class, as a {@link Class} instance 
if possible.
+     * @param  count    length of the array of type parameters in which to 
select a single type.
+     * @return index of the parameter to select in an array of length {@code 
count}, or -1 if none.
+     */
+    @SuppressWarnings("fallthrough")
+    private static int chooseSingleType(final Object rawType, final int count) 
{
+        switch (count) {
+            case 2: if (!(rawType instanceof Class<?>) && 
Map.class.isAssignableFrom((Class<?>) rawType)) break;
+                    // Else fallthrough.
+            case 1: return 0;
+        }
+        return -1;
+    }
+
     /**
      * Returns the class of the specified object, or {@code null} if {@code 
object} is null.
      * This method is also useful for fetching the class of an object known 
only by its bound
diff --git 
a/core/sis-utility/src/test/java/org/apache/sis/util/ClassesTest.java 
b/core/sis-utility/src/test/java/org/apache/sis/util/ClassesTest.java
index 7d608db323..d6903c1f67 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/util/ClassesTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/util/ClassesTest.java
@@ -17,6 +17,8 @@
 package org.apache.sis.util;
 
 import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.GenericDeclaration;
 import org.junit.Test;
 import org.apache.sis.test.TestCase;
 
@@ -28,6 +30,7 @@ import static org.apache.sis.util.Classes.*;
  * The are used only as various Class<?> arguments
  * given to the methods to test.
  */
+import java.util.Map;
 import java.util.Set;
 import java.util.List;
 import java.util.HashSet;
@@ -44,6 +47,8 @@ import java.io.InvalidObjectException;
 import java.io.NotSerializableException;
 import java.io.Serializable;
 import java.awt.geom.Point2D;
+import javax.print.attribute.standard.PrinterStateReason;
+import javax.print.attribute.standard.PrinterStateReasons;
 import org.opengis.util.InternationalString;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.referencing.IdentifiedObject;
@@ -62,7 +67,7 @@ import org.opengis.referencing.operation.CoordinateOperation;
  * Tests the {@link Classes} static methods.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.1
+ * @version 1.3
  * @since   0.3
  * @module
  */
@@ -94,7 +99,7 @@ public final strictfp class ClassesTest extends TestCase {
      * Tests {@link Classes#getAllInterfaces(Class)}.
      */
     @Test
-    @SuppressWarnings("")
+    @SuppressWarnings("rawtypes")
     public void testGetAllInterfaces() {
         assertArrayEquals(new Class[] {
             GeographicCRS.class,
@@ -221,25 +226,39 @@ public final strictfp class ClassesTest extends TestCase {
     /**
      * Tests the {@link Classes#boundOfParameterizedProperty(Field)} method.
      *
-     * @throws NoSuchFieldException  if there is an error in a field name.
+     * @throws NoSuchFieldException if there is an error in a field name.
+     */
+    @Test
+    public void testBoundOfParameterizedField() throws NoSuchFieldException {
+        final Class<Parameterized> c = Parameterized.class;
+        assertNull(                
boundOfParameterizedProperty(c.getField("attrib1")));
+        assertEquals(Long  .class, 
boundOfParameterizedProperty(c.getField("attrib2")));
+        assertEquals(String.class, 
boundOfParameterizedProperty(c.getField("attrib3")));
+    }
+
+    /**
+     * Tests the {@link Classes#boundOfParameterizedProperty(Method)} method.
+     *
      * @throws NoSuchMethodException if there is an error in a method name.
      */
     @Test
-    public void testBoundOfParameterizedProperty() throws 
NoSuchFieldException, NoSuchMethodException {
-        final Class<?>[] g = null;
-        final Class<?>[] s = new Class<?>[] {Set.class};
+    public void testBoundOfParameterizedProperty() throws 
NoSuchMethodException {
+        final Class<?>[] getter = null;
+        final Class<?>[] setter = new Class<?>[] {Set.class};
         final Class<Parameterized> c = Parameterized.class;
-        assertNull(                    
boundOfParameterizedProperty(c.getMethod("getter0", g)));
-        assertNull(                    
boundOfParameterizedProperty(c.getMethod("setter0", s)));
-        assertEquals(Long      .class, boundOfParameterizedProperty(c.getField 
("attrib2"   )));
-        assertEquals(Integer   .class, 
boundOfParameterizedProperty(c.getMethod("getter1", g)));
-        assertEquals(Byte      .class, 
boundOfParameterizedProperty(c.getMethod("getter2", g)));
-        assertEquals(Object    .class, 
boundOfParameterizedProperty(c.getMethod("getter3", g)));
-        assertEquals(short[]   .class, 
boundOfParameterizedProperty(c.getMethod("getter4", g)));
-        assertEquals(Comparable.class, 
boundOfParameterizedProperty(c.getMethod("getter5", g)));
-        assertEquals(String    .class, 
boundOfParameterizedProperty(c.getMethod("setter1", s)));
-        assertEquals(Short     .class, 
boundOfParameterizedProperty(c.getMethod("setter2", s)));
-        assertEquals(Object    .class, 
boundOfParameterizedProperty(c.getMethod("setter3", s)));
+        assertNull(                      
boundOfParameterizedProperty(c.getMethod("getter0", getter)));
+        assertNull(                      
boundOfParameterizedProperty(c.getMethod("setter0", setter)));
+        assertEquals(Integer     .class, 
boundOfParameterizedProperty(c.getMethod("getter1", getter)));
+        assertEquals(Byte        .class, 
boundOfParameterizedProperty(c.getMethod("getter2", getter)));
+        assertEquals(Object      .class, 
boundOfParameterizedProperty(c.getMethod("getter3", getter)));
+        assertEquals(short[]     .class, 
boundOfParameterizedProperty(c.getMethod("getter4", getter)));
+        assertEquals(Comparable  .class, 
boundOfParameterizedProperty(c.getMethod("getter5", getter)));
+        assertEquals(Comparable[].class, 
boundOfParameterizedProperty(c.getMethod("getter6", getter)));
+        assertEquals(String      .class, 
boundOfParameterizedProperty(c.getMethod("setter1", setter)));
+        assertEquals(Short       .class, 
boundOfParameterizedProperty(c.getMethod("setter2", setter)));
+        assertEquals(Object      .class, 
boundOfParameterizedProperty(c.getMethod("setter3", setter)));
+
+        assertEquals(PrinterStateReason.class, 
boundOfParameterizedProperty(c.getMethod("getter7", getter)));
     }
 
     /**
@@ -247,20 +266,35 @@ public final strictfp class ClassesTest extends TestCase {
      */
     @SuppressWarnings("rawtypes")
     private static final class Parameterized {
-        public Set<? extends Long> attrib2 = null;
-        public Set                 getter0() {return null;}         // 
Intentionnaly unparameterized.
-        public Set<       Integer> getter1() {return null;}
-        public Set<? extends Byte> getter2() {return null;}
-        public Set<? super  Float> getter3() {return null;}
-        public Set<       short[]> getter4() {return null;}
-        public Set<Comparable<?>>  getter5() {return null;}
-
-        public void setter0(Set                  dummy) {}          // 
Intentionnaly unparameterized.
+        public Long                 attrib1;
+        public Set<? extends Long>  attrib2;
+        public Map<String,Integer>  attrib3;
+        public Set                  getter0() {return null;}        // 
Intentionnaly unparameterized.
+        public Set<       Integer>  getter1() {return null;}
+        public Set<? extends Byte>  getter2() {return null;}
+        public Set<? super  Float>  getter3() {return null;}
+        public Set<       short[]>  getter4() {return null;}
+        public Set<Comparable<?>>   getter5() {return null;}
+        public Set<Comparable<?>[]> getter6() {return null;}
+        public PrinterStateReasons  getter7() {return null;}
+
+        public void setter0(Set                  dummy) {}         // 
Intentionnaly unparameterized.
         public void setter1(Set<         String> dummy) {}
         public void setter2(Set<? extends Short> dummy) {}
         public void setter3(Set<? super  Double> dummy) {}
     }
 
+    /**
+     * Tests the {@link 
Classes#boundOfParameterizedDeclaration(GenericDeclaration)} method.
+     */
+    @Test
+    public void testBoundOfParameterizedDeclaration() {
+        assertNull  (              
boundOfParameterizedDeclaration(Long.class));
+        assertEquals(Object.class, 
boundOfParameterizedDeclaration(List.class));
+        assertEquals(Object.class, boundOfParameterizedDeclaration(Map.class));
+        assertEquals(PrinterStateReason.class, 
boundOfParameterizedDeclaration(PrinterStateReasons.class));
+    }
+
     /**
      * Tests the {@link Classes#getShortName(Class)}, in particular the 
example values given in the javadoc.
      */


Reply via email to