blob: 66d0163459c47616d01ed53b76b5ea6a9695a3ce [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.util;
import java.util.Set;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.lang.reflect.Type;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.WildcardType;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Modifier;
import org.opengis.annotation.UML;
import static org.apache.sis.util.collection.Containers.hashMapCapacity;
import static org.apache.sis.internal.system.Modules.INTERNAL_CLASSNAME_PREFIX;
/**
* Static methods working on {@link Class} objects.
* This class defines helper methods for working with reflection.
* Some functionalities are:
*
* <ul>
* <li>Add or remove dimension to an array type
* ({@link #changeArrayDimension(Class, int) changeArrayDimension})</li>
* <li>Find the common parent of two or more classes
* ({@link #findCommonClass(Class, Class) findCommonClass},
* {@link #findCommonInterfaces(Class, Class) findCommonInterfaces})</li>
* <li>Getting the bounds of a parameterized field or method
* ({@link #boundOfParameterizedProperty(Method) boundOfParameterizedProperty})</li>
* <li>Getting a short class name ({@link #getShortName(Class) getShortName},
* {@link #getShortClassName(Object) getShortClassName})</li>
* </ul>
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 1.0
* @since 0.3
* @module
*/
public final class Classes extends Static {
/**
* An empty array of classes.
*/
@SuppressWarnings({"unchecked","rawtypes"})
private static final Class<Object>[] EMPTY_ARRAY = new Class[0];
/**
* Methods to be rejected by {@link #isPossibleGetter(Method)}. They are mostly methods inherited
* from {@link Object}. Only no-argument methods having a non-void return value need to be
* declared in this list.
*
* <p>Note that testing {@code type.getDeclaringClass().equals(Object.class)}
* is not sufficient because those methods may be overridden in subclasses.</p>
*/
private static final String[] EXCLUDES = {
"clone", "getClass", "hashCode", "toString", "toWKT"
};
/**
* Do not allow instantiation of this class.
*/
private Classes() {
}
/**
* Changes the array dimension by the given amount. The given class can be a primitive type,
* a Java object, or an array of the above. If the given {@code change} is positive, then the
* array dimension will be increased by that amount. For example a change of +1 dimension will
* change an {@code int} class into {@code int[]}, and a {@code String[]} class into {@code String[][]}.
* A change of +2 dimensions is like applying two times a change of +1 dimension.
*
* <p>The change of dimension can also be negative. For example a change of -1 dimension will
* change a {@code String[]} class into a {@code String}. More specifically:</p>
*
* <ul>
* <li>If the given {@code element} is null, then this method returns {@code null}.</li>
* <li>Otherwise if the given {@code change} is 0, then the given {@code element} is returned unchanged.</li>
* <li>Otherwise if the given {@code change} is negative, then {@link Class#getComponentType()} is invoked
* {@code abs(change)} times. The result is a {@code null} value if {@code abs(change)} is greater than
* the array dimension.</li>
* <li>Otherwise if {@code element} is {@link Void#TYPE}, then this method returns {@code Void.TYPE}
* since arrays of {@code void} do not exist.</li>
* <li>Otherwise this method returns a class that represents an array of the given class augmented by
* the given amount of dimensions.</li>
* </ul>
*
* @param element the type of elements in the array.
* @param change the change of dimension, as a negative or positive number.
* @return the type of an array of the given element type augmented by the given
* number of dimensions (which may be negative), or {@code null}.
*/
public static Class<?> changeArrayDimension(Class<?> element, int change) {
if (change != 0 && element != null) {
if (change < 0) {
do element = element.getComponentType();
while (element!=null && ++change != 0);
} else if (element != Void.TYPE) {
final StringBuilder buffer = new StringBuilder();
do buffer.insert(0, '[');
while (--change != 0);
if (element.isPrimitive()) {
buffer.append(Numbers.getInternal(element));
} else if (element.isArray()) {
buffer.append(element.getName());
} else {
buffer.append('L').append(element.getName()).append(';');
}
final String name = buffer.toString();
try {
element = Class.forName(name);
} catch (ClassNotFoundException e) {
throw new TypeNotPresentException(name, e);
// Should never happen because we are creating an array of an existing class.
}
}
}
return element;
}
/**
* 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:
* <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>{@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 un-parameterized.</li>
* </ul>
*
* @param field the field for which to obtain the parameterized type.
* @return the upper bound of parameterized type, or {@code null} if the given field
* is not of a parameterized type.
*/
public static Class<?> boundOfParameterizedProperty(final Field field) {
return getActualTypeArgument(field.getGenericType());
}
/**
* 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.
*
* <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>
*
* @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.
*/
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]);
}
}
return c;
}
/**
* 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}.
*/
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;
}
/*
* At this point we are not going to continue the loop anymore.
* Check if we have an array, then check the (component) 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++;
}
if (type instanceof Class<?>) {
return changeArrayDimension((Class<?>) type, dimension);
}
break; // Unknown type.
}
}
return null;
}
/**
* 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
* type. As of Java 6, the usual pattern:
*
* {@preformat java
* Number n = 0;
* Class<? extends Number> c = n.getClass();
* }
*
* doesn't seem to work if {@link Number} is replaced by a parameterized type {@code T}.
*
* @param <T> the type of the given object.
* @param object the object for which to get the class, or {@code null}.
* @return the class of the given object, or {@code null} if the given object was null.
*/
@SuppressWarnings("unchecked")
@Workaround(library="JDK", version="1.7")
public static <T> Class<? extends T> getClass(final T object) {
return (object != null) ? (Class<? extends T>) object.getClass() : null;
}
/**
* Returns the classes of all objects in the given collection. If the given collection
* contains some null elements, then the returned set will contain a null element as well.
* The returned set is modifiable and can be freely updated by the caller.
*
* <p>Note that interfaces are not included in the returned set.</p>
*
* @param <T> the base type of elements in the given collection.
* @param objects the collection of objects.
* @return the set of classes of all objects in the given collection.
*/
private static <T> Set<Class<? extends T>> getClasses(final Iterable<? extends T> objects) {
final Set<Class<? extends T>> types = new LinkedHashSet<>();
for (final T object : objects) {
types.add(getClass(object));
}
return types;
}
/**
* Returns the first type or super-type (including interface) considered "standard" in Apache SIS sense.
* This method applies the following heuristic rules, in that order:
*
* <ul>
* <li>If the given type implements at least one interface having the {@link UML} annotation,
* then the first annotated interface is returned.</li>
* <li>Otherwise the first public class or parent class is returned.</li>
* </ul>
*
* Those heuristic rules may be adjusted in any future Apache SIS version.
*
* @param <T> the compile-time type argument.
* @param type the type for which to get the standard interface or class. May be {@code null}.
* @return a standard interface implemented by {@code type}, or otherwise the most specific public class.
* Is {@code null} if the given {@code type} argument was null.
*
* @since 1.0
*/
public static <T> Class<? super T> getStandardType(final Class<T> type) {
for (final Class<? super T> candidate : getAllInterfaces(type)) {
if (candidate.isAnnotationPresent(UML.class)) {
return candidate;
}
}
for (Class<? super T> candidate = type; candidate != null; candidate = candidate.getSuperclass()) {
if (Modifier.isPublic(candidate.getModifiers()) && !candidate.getName().startsWith(INTERNAL_CLASSNAME_PREFIX)) {
return candidate;
}
}
return type;
}
/**
* Returns every interfaces implemented, directly or indirectly, by the given class or interface.
* This is similar to {@link Class#getInterfaces()} except that this method searches recursively
* in the super-interfaces. For example if the given type is {@link java.util.ArrayList}, then
* the returned set will contain {@link java.util.List} (which is implemented directly)
* together with its parent interfaces {@link Collection} and {@link Iterable}.
*
* @param <T> the compile-time type of the {@code Class} argument.
* @param type the class or interface for which to get all implemented interfaces.
* @return all implemented interfaces (not including the given {@code type} if it was an interface),
* or an empty array if none.
*
* @see Class#getInterfaces()
*/
@SuppressWarnings({"unchecked","rawtypes"}) // Generic array creation.
public static <T> Class<? super T>[] getAllInterfaces(final Class<T> type) {
final Set<Class<?>> interfaces = getInterfaceSet(type);
return (interfaces != null) ? interfaces.toArray(new Class[interfaces.size()]) : EMPTY_ARRAY;
}
/**
* Implementation of {@link #getAllInterfaces(Class)} returning a {@link Set}.
* The public API exposes the method returning an array instead than a set for
* the following reasons:
*
* <ul>
* <li>Consistency with other methods ({@link #getLeafInterfaces(Class, Class)},
* {@link Class#getInterfaces()}).</li>
* <li>Because arrays in Java are covariant, while the {@code Set} are not.
* Consequently callers can cast {@code Class<? super T>[]} to {@code Class<?>[]}
* while they can not cast {@code Set<Class<? super T>>} to {@code Set<Class<?>>}.</li>
* </ul>
*
* @param type the class or interface for which to get all implemented interfaces.
* @return all implemented interfaces (not including the given {@code type} if it was an interface),
* or {@code null} if none. Callers can freely modify the returned set.
*/
static Set<Class<?>> getInterfaceSet(Class<?> type) {
Set<Class<?>> interfaces = null;
while (type != null) {
interfaces = getInterfaceSet(type, interfaces);
type = type.getSuperclass();
}
return interfaces;
}
/**
* Adds to the given set every interfaces implemented by the given class or interface.
*
* @param type the type for which to add the interfaces in the given set.
* @param addTo the set where to add interfaces, or {@code null} if not yet created.
* @return the given set (may be {@code null}), or a new set if the given set was null
* and at least one interface has been found.
*/
private static Set<Class<?>> getInterfaceSet(final Class<?> type, Set<Class<?>> addTo) {
final Class<?>[] interfaces = type.getInterfaces();
for (final Class<?> candidate : interfaces) {
if (addTo == null) {
addTo = new LinkedHashSet<>(hashMapCapacity(interfaces.length));
}
if (addTo.add(candidate)) {
getInterfaceSet(candidate, addTo);
}
}
return addTo;
}
/**
* Returns the interfaces implemented by the given class and assignable to the given base
* interface, or an empty array if none. If more than one interface extends the given base,
* then the most specialized interfaces are returned. For example if the given class
* implements both the {@link Set} and {@link Collection} interfaces, then the returned
* array contains only the {@code Set} interface.
*
* <h4>Example</h4>
* {@code getLeafInterfaces(ArrayList.class, Collection.class)} returns an array of length 1
* containing {@code List.class}.
*
* @param <T> the type of the {@code baseInterface} class argument.
* @param type a class for which the implemented interfaces are desired, or {@code null}.
* @param baseInterface the base type of the interfaces to search.
* @return the leaf interfaces matching the given criterion, or an empty array if none.
*/
@SuppressWarnings("unchecked")
public static <T> Class<? extends T>[] getLeafInterfaces(Class<?> type, final Class<T> baseInterface) {
int count = 0;
Class<?>[] types = EMPTY_ARRAY;
while (type != null) {
final Class<?>[] candidates = type.getInterfaces();
next: for (final Class<?> candidate : candidates) {
if (baseInterface == null || baseInterface.isAssignableFrom(candidate)) {
/*
* At this point, we have an interface to be included in the returned array.
* If a more specialized interface existed before 'candidate', forget the
* candidate.
*/
for (int i=0; i<count; i++) {
final Class<?> old = types[i];
if (candidate.isAssignableFrom(old)) {
continue next; // A more specialized interface already exists.
}
if (old.isAssignableFrom(candidate)) {
types[i] = candidate; // This interface specializes a previous interface.
continue next;
}
}
if (types == EMPTY_ARRAY) {
types = candidates;
}
if (count >= types.length) {
types = Arrays.copyOf(types, types.length + candidates.length);
}
types[count++] = candidate;
}
}
type = type.getSuperclass();
}
return (Class[]) ArraysExt.resize(types, count);
}
/**
* Returns the most specific class implemented by the objects in the given collection.
* If there is more than one specialized class, returns their {@linkplain #findCommonClass
* most specific common super class}.
*
* <p>This method searches for classes only, not interfaces.</p>
*
* @param objects a collection of objects. May contains duplicated values and null values.
* @return the most specialized class, or {@code null} if the given collection does not contain
* at least one non-null element.
*/
public static Class<?> findSpecializedClass(final Iterable<?> objects) {
final Set<Class<?>> types = getClasses(objects);
types.remove(null);
/*
* Removes every classes in the types collection which are assignable from an other
* class from the same collection. As a result, the collection should contains only
* leaf classes.
*/
for (final Iterator<Class<?>> it=types.iterator(); it.hasNext();) {
final Class<?> candidate = it.next();
for (final Class<?> type : types) {
if (candidate != type && candidate.isAssignableFrom(type)) {
it.remove();
break;
}
}
}
return common(types);
}
/**
* Returns the most specific class which is a common parent of all the specified classes.
* This method is not public in order to make sure that it contains only classes, not
* interfaces, since our implementation is not designed for multi-inheritances.
*
* @param types the collection where to search for a common parent.
* @return the common parent, or {@code null} if the given collection is empty.
*/
private static Class<?> common(final Set<Class<?>> types) {
final Iterator<Class<?>> it = types.iterator();
if (!it.hasNext()) {
return null;
}
Class<?> type = it.next();
while (it.hasNext()) {
type = findCommonClass(type, it.next());
}
return type;
}
/**
* Returns the most specific class which {@linkplain Class#isAssignableFrom is assignable from}
* the type of all given objects. If no element in the given collection has a type assignable
* from the type of all other elements, then this method searches for a common
* {@linkplain Class#getSuperclass super class}.
*
* <p>This method searches for classes only, not interfaces.</p>
*
* @param objects a collection of objects. May contains duplicated values and null values.
* @return the most specific class common to all supplied objects, or {@code null} if the
* given collection does not contain at least one non-null element.
*/
public static Class<?> findCommonClass(final Iterable<?> objects) {
final Set<Class<?>> types = getClasses(objects);
types.remove(null);
return common(types);
}
/**
* Returns the most specific class which {@linkplain Class#isAssignableFrom is assignable from}
* the given classes or a parent of those classes. This method returns either {@code c1},
* {@code c2} or a common parent of {@code c1} and {@code c2}.
*
* <p>This method considers classes only, not the interfaces.</p>
*
* @param c1 the first class, or {@code null}.
* @param c2 the second class, or {@code null}.
* @return the most specific class common to the supplied classes, or {@code null}
* if both {@code c1} and {@code c2} are null.
*/
public static Class<?> findCommonClass(Class<?> c1, Class<?> c2) {
if (c1 == null) return c2;
if (c2 == null) return c1;
do {
if (c1.isAssignableFrom(c2)) {
return c1;
}
if (c2.isAssignableFrom(c1)) {
return c2;
}
c1 = c1.getSuperclass();
c2 = c2.getSuperclass();
} while (c1 != null && c2 != null);
return Object.class;
}
/**
* Returns the interfaces which are implemented by the two given classes. The returned set
* does not include the parent interfaces. For example if the two given objects implement the
* {@link Collection} interface, then the returned set will contain the {@code Collection}
* type but not the {@link Iterable} type, since it is implied by the collection type.
*
* @param c1 the first class.
* @param c2 the second class.
* @return the interfaces common to both classes, or an empty set if none.
* Callers can freely modify the returned set.
*/
public static Set<Class<?>> findCommonInterfaces(final Class<?> c1, final Class<?> c2) {
final Set<Class<?>> interfaces = getInterfaceSet(c1);
final Set<Class<?>> buffer = getInterfaceSet(c2); // To be recycled.
if (interfaces == null || buffer == null) {
return Collections.emptySet();
}
interfaces.retainAll(buffer);
for (Iterator<Class<?>> it=interfaces.iterator(); it.hasNext();) {
final Class<?> candidate = it.next();
buffer.clear(); // Safe because the buffer can not be Collections.EMPTY_SET at this point.
getInterfaceSet(candidate, buffer);
if (interfaces.removeAll(buffer)) {
it = interfaces.iterator();
}
}
return interfaces;
}
/**
* Returns {@code true} if the two specified objects implements exactly the same set
* of interfaces. Only interfaces assignable to {@code baseInterface} are compared.
* Declaration order doesn't matter.
*
* <div class="note"><b>Example:</b>
* in ISO 19111, different interfaces exist for different coordinate system (CS) geometries
* ({@code CartesianCS}, {@code PolarCS}, etc.). One can check if two implementations have
* the same geometry with the following code:
*
* {@preformat java
* if (implementSameInterfaces(cs1, cs2, CoordinateSystem.class)) {
* // The two Coordinate System are of the same kind.
* }
* }
* </div>
*
* @param object1 the first object to check for interfaces.
* @param object2 the second object to check for interfaces.
* @param baseInterface the parent of all interfaces to check.
* @return {@code true} if both objects implement the same set of interfaces,
* considering only sub-interfaces of {@code baseInterface}.
*/
public static boolean implementSameInterfaces(final Class<?> object1, final Class<?> object2, final Class<?> baseInterface) {
if (object1 == object2) {
return true;
}
if (object1 == null || object2 == null) {
return false;
}
final Class<?>[] c1 = getLeafInterfaces(object1, baseInterface);
final Class<?>[] c2 = getLeafInterfaces(object2, baseInterface);
/*
* For each interface in the 'c1' array, check if
* this interface exists also in the 'c2' array.
*/
int n = c2.length;
cmp: for (final Class<?> c : c1) {
for (int j=n; --j>=0;) {
if (c == c2[j]) {
System.arraycopy(c2, j+1, c2, j, --n-j);
continue cmp;
}
}
return false; // Interface not found in 'c2'.
}
return n == 0; // If n>0, at least one interface was not found in 'c1'.
}
/**
* Returns the name of the given class without package name, but including the names of enclosing
* classes if any. This method is similar to the {@link Class#getSimpleName()} method, except that
* if the given class is an inner class, then the returned value is prefixed with the outer class
* name. An other difference is that if the given class is local or anonymous, then this method
* returns the name of the parent class.
*
* <p>The following table compares the various kind of names for some examples:</p>
*
* <table class="sis">
* <caption>Class name comparisons</caption>
* <tr>
* <th>Class</th>
* <th>{@code getName()}</th>
* <th>{@code getSimpleName()}</th>
* <th>{@code getCanonicalName()}</th>
* <th>{@code getShortName()}</th>
* </tr>
* <tr>
* <td>{@link String}</td>
* <td>{@code "java.lang.String"}</td>
* <td>{@code "String"}</td>
* <td>{@code "java.lang.String"}</td>
* <td>{@code "String"}</td>
* </tr>
* <tr>
* <td>{@code double[]}</td>
* <td>{@code "[D"}</td>
* <td>{@code "double[]"}</td>
* <td>{@code "double[]"}</td>
* <td>{@code "double[]"}</td>
* </tr>
* <tr>
* <td>{@link java.awt.geom.Point2D.Double}</td>
* <td>{@code "java.awt.geom.Point2D$Double"}</td>
* <td>{@code "Double"}</td>
* <td>{@code "java.awt.geom.Point2D.Double"}</td>
* <td>{@code "Point2D.Double"}</td>
* </tr>
* <tr>
* <td>Anonymous {@link Comparable}</td>
* <td>{@code "com.mycompany.myclass$1"}</td>
* <td>{@code ""}</td>
* <td>{@code null}</td>
* <td>{@code "Object"}</td>
* </tr>
* </table>
*
* @param classe the object class (may be {@code null}).
* @return the simple name with outer class name (if any) of the first non-anonymous
* class in the hierarchy, or {@code "<*>"} if the given class is null.
*
* @see #getShortClassName(Object)
* @see Class#getSimpleName()
*/
public static String getShortName(Class<?> classe) {
if (classe == null) {
return "<*>";
}
while (classe.isAnonymousClass()) {
classe = classe.getSuperclass();
}
String name = classe.getSimpleName();
final Class<?> enclosing = classe.getEnclosingClass();
if (enclosing != null) {
name = getShortName(enclosing) + '.' + name;
}
return name;
}
/**
* Returns the class name of the given object without package name, but including the enclosing class names
* if any. Invoking this method is equivalent to invoking {@code getShortName(object.getClass())} except for
* {@code null} value. See {@link #getShortName(Class)} for more information on the class name returned by
* this method.
*
* @param object the object (may be {@code null}).
* @return the simple class name with outer class name (if any) of the first non-anonymous
* class in the hierarchy, or {@code "<*>"} if the given object is null.
*
* @see #getShortName(Class)
*/
public static String getShortClassName(final Object object) {
return getShortName(getClass(object));
}
/**
* Returns {@code true} if the given type is assignable to one of the given allowed types.
* More specifically, if at least one {@code allowedTypes[i]} element exists for which
* <code>allowedTypes[i].{@linkplain Class#isAssignableFrom(Class) isAssignableFrom}(type)</code>
* returns {@code true}, then this method returns {@code true}.
*
* <p>Special cases:</p>
* <ul>
* <li>If {@code type} is null, then this method returns {@code false}.</li>
* <li>If {@code allowedTypes} is null, then this method returns {@code true}.
* This is to be interpreted as "no restriction on the allowed types".</li>
* <li>Any null element in the {@code allowedTypes} array are silently ignored.</li>
* </ul>
*
* @param type the type to be tested, or {@code null}.
* @param allowedTypes the allowed types.
* @return {@code true} if the given type is assignable to one of the allowed types.
*/
public static boolean isAssignableToAny(final Class<?> type, final Class<?>... allowedTypes) {
if (type != null) {
if (allowedTypes == null) {
return true;
}
for (final Class<?> candidate : allowedTypes) {
if (candidate != null && candidate.isAssignableFrom(type)) {
return true;
}
}
}
return false;
}
/**
* Returns {@code true} if the given method may possibly be the getter method for a property.
* This method implements the algorithm used by SIS in order to identify getter methods in
* {@linkplain org.opengis.metadata metadata} interfaces. We do not rely on naming convention
* (method names starting with "{@code get}" or "{@code is}" prefixes) because not every methods
* follow such convention (e.g. {@link org.opengis.metadata.quality.ConformanceResult#pass()}).
*
* <p>The current implementation returns {@code true} if the given method meets all the
* following conditions. Note that a {@code true} value is not a guaranteed that the given
* method is really a getter. The caller is encouraged to perform additional checks if
* possible.</p>
*
* <ul>
* <li>The method does no expect any argument.</li>
* <li>The method returns a value (anything except {@code void}).</li>
* <li>The method name is not {@link Object#clone() clone}, {@link Object#getClass() getClass},
* {@link Object#hashCode() hashCode}, {@link Object#toString() toString} or
* {@link org.opengis.referencing.IdentifiedObject#toWKT() toWKT}.</li>
* <li>The method is not {@linkplain Method#isSynthetic() synthetic}.</li>
* </ul>
*
* <p>Those conditions may be updated in any future SIS version.</p>
*
* @param method the method to inspect.
* @return {@code true} if the given method may possibly be a non-deprecated getter method.
*/
public static boolean isPossibleGetter(final Method method) {
return method.getReturnType() != Void.TYPE &&
method.getParameterCount() == 0 &&
!method.isSynthetic() &&
!ArraysExt.contains(EXCLUDES, method.getName());
}
}