/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.resources;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.lang.reflect.Type;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.WildcardType;
import java.lang.reflect.ParameterizedType;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
/**
* A set of miscellaneous methods working on {@link Class} objects.
*
* @since 2.5
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public final class Classes {
/**
* Constants to be used in {@code switch} statements.
*/
public static final byte DOUBLE=8, FLOAT=7, LONG=6, INTEGER=5, SHORT=4, BYTE=3,
CHARACTER=2, BOOLEAN=1, OTHER=0;
/**
* Mapping between a primitive type and its wrapper, if any.
*/
private static final Map<Class<?>,Classes> MAPPING = new HashMap<Class<?>,Classes>(16);
static {
new Classes(Double .TYPE, Double .class, true, false, (byte) Double .SIZE, DOUBLE );
new Classes(Float .TYPE, Float .class, true, false, (byte) Float .SIZE, FLOAT );
new Classes(Long .TYPE, Long .class, false, true, (byte) Long .SIZE, LONG );
new Classes(Integer .TYPE, Integer .class, false, true, (byte) Integer .SIZE, INTEGER );
new Classes(Short .TYPE, Short .class, false, true, (byte) Short .SIZE, SHORT );
new Classes(Byte .TYPE, Byte .class, false, true, (byte) Byte .SIZE, BYTE );
new Classes(Character.TYPE, Character.class, false, false, (byte) Character.SIZE, CHARACTER);
new Classes(Boolean .TYPE, Boolean .class, false, false, (byte) 1, BOOLEAN );
new Classes(Void .TYPE, Void .class, false, false, (byte) 0, OTHER );
}
/** The primitive type. */ private final Class<?> primitive;
/** The wrapper for the primitive type. */ private final Class<?> wrapper;
/** {@code true} for floating point number. */ private final boolean isFloat;
/** {@code true} for integer number. */ private final boolean isInteger;
/** The size in bytes. */ private final byte size;
/** Constant to be used in switch statement.*/ private final byte ordinal;
/**
* Creates a mapping between a primitive type and its wrapper.
*/
private Classes(final Class<?> primitive, final Class<?> wrapper,
final boolean isFloat, final boolean isInteger,
final byte size, final byte ordinal)
{
this.primitive = primitive;
this.wrapper = wrapper;
this.isFloat = isFloat;
this.isInteger = isInteger;
this.size = size;
this.ordinal = ordinal;
if (MAPPING.put(primitive, this) != null || MAPPING.put(wrapper, this) != null) {
throw new AssertionError(); // Should never happen.
}
}
/**
* Returns the upper bounds of the parameterized type of the given attribute.
* If the attribute 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<?> boundOfParameterizedAttribute(final Field field) {
return getActualTypeArgument(field.getGenericType());
}
/**
* If the given method is a getter or a setter for a parameterized attribute, returns the
* upper bounds of the parameterized type. Otherwise returns {@code null}. This method
* provides the same semantic than {@link #boundOfParameterizedAttribute(Field)}, but
* works on a getter or setter method rather then the field. See the javadoc of above
* methods 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).
*
* @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
* do not opperate on an object of a parameterized type.
*/
public static Class<?> boundOfParameterizedAttribute(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 Class) {
return (Class) type;
} else if (type instanceof WildcardType) {
p = ((WildcardType) type).getUpperBounds();
} else {
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:
*
* <blockquote><pre>
* Number n = 0;
* Class<? extends Number> c = n.getClass();
* </pre></blockquote>
*
* doesn't seem to work if {@link Number} is replaced by a parametirez 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")
public static <T> Class<? extends T> getClass(final T object) {
return (object != null) ? (Class<? extends T>) object.getClass() : null;
}
/**
* Returns all classes implemented by the given set of objects.
*/
private static Set<Class<?>> getClasses(final Collection<?> objects) {
final Set<Class<?>> types = new LinkedHashSet<Class<?>>();
for (final Object object : objects) {
if (object != null) {
types.add(object.getClass());
}
}
return types;
}
/**
* Returns the most specific class implemented by the objects in the given collection.
* If no class are {@linkplain Class#isAssignableFrom assignable} to all others, then
* this method returns the {@linkplain #commonClass most specific common super class}.
*
* @param objects A collection of objects. May contains duplicated values and null values.
* @return The most specific class.
*/
public static Class<?> specializedClass(final Collection<?> objects) {
final Set<Class<?>> types = getClasses(objects);
final Class<?> type = removeAssignables(types);
return (type != null) ? type : commonSuperClass(types);
}
/**
* Returns the most specific class which is a common parent of all specified objects.
*
* @param objects A collection of objects. May contains duplicated values and null values.
* @return The most specific class common to all supplied objects.
*/
public static Class<?> commonClass(final Collection<?> objects) {
final Set<Class<?>> types = getClasses(objects);
/*
* First check if a type is assignable from all other types. At most one such
* type can exists. We check for it first in order to avoid the creation of a
* temporary HashSet if such type is found.
*/
search: for (final Class<?> candidate : types) {
for (final Class<?> type : types) {
if (!candidate.isAssignableFrom(type)) {
continue search;
}
}
return candidate;
}
return commonSuperClass(types);
}
/**
* Returns the most specific class which is a common parent of all the specified classes.
* This method should be invoked when no common parent has been found in the supplied list.
*/
private static Class<?> commonSuperClass(final Collection<Class<?>> types) {
// Build a list of all super classes.
final Set<Class<?>> superTypes = new LinkedHashSet<Class<?>>();
for (Class<?> type : types) {
while ((type = type.getSuperclass()) != null) {
if (!superTypes.add(type)) {
// If the type was already in the set, then its super-types are in the set too.
break;
}
}
}
// Removes every elements that are not assignable from every supplied types.
for (final Iterator<Class<?>> it=superTypes.iterator(); it.hasNext();) {
final Class<?> candidate = it.next();
for (final Class<?> type : types) {
if (!candidate.isAssignableFrom(type)) {
it.remove();
break;
}
}
}
// Now removes every classes that can be assigned from an other classes.
// We should have only one left, the most specific one in the hierarchy.
return removeAssignables(superTypes);
}
/**
* Removes every classes in the specified collection which are assignable from an other
* class from the same collection. As a result of this method call, the given collection
* should contains only leaf classes.
*
* @param types The collection to trim.
* @return If there is exactly one element left, that element. Otherwise {@code null}.
*/
private static Class<?> removeAssignables(final Collection<Class<?>> types) {
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 (types.size() == 1) ? types.iterator().next() : null;
}
/**
* Returns {@code true} if the two specified objects implements exactly the same set of
* interfaces. Only interfaces assignable to {@code base} are compared. Declaration order
* doesn't matter. For example in ISO 19111, different interfaces exist for different coordinate
* system geometries ({@code CartesianCS}, {@code PolarCS}, etc.). We can check if two
* CS implementations has the same geometry with the following code:
*
* <blockquote><code>
* if (sameInterfaces(cs1, cs2, {@linkplain org.opengis.referencing.cs.CoordinateSystem}.class))
* </code></blockquote>
*
* @param <T> A common parent for both objects.
* @param object1 The first object to check for interfaces.
* @param object2 The second object to check for interfaces.
* @param base 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 base}.
*/
public static <T> boolean sameInterfaces(final Class<? extends T> object1,
final Class<? extends T> object2,
final Class<T> base)
{
if (object1 == object2) {
return true;
}
if (object1==null || object2==null) {
return false;
}
final Class<?>[] c1 = object1.getInterfaces();
final Class<?>[] c2 = object2.getInterfaces();
/*
* Trim all interfaces that are not assignable to 'base' in the 'c2' array.
* Doing this once will avoid to redo the same test many time in the inner
* loops j=[0..n].
*/
int n = 0;
for (int i=0; i<c2.length; i++) {
final Class<?> c = c2[i];
if (base.isAssignableFrom(c)) {
c2[n++] = c;
}
}
/*
* For each interface assignable to 'base' in the 'c1' array, check if
* this interface exists also in the 'c2' array. Order doesn't matter.
*/
compare:for (int i=0; i<c1.length; i++) {
final Class<?> c = c1[i];
if (base.isAssignableFrom(c)) {
for (int j=0; j<n; j++) {
if (c.equals(c2[j])) {
System.arraycopy(c2, j+1, c2, j, --n-j);
continue compare;
}
}
return false; // Interface not found in 'c2'.
}
}
return n == 0; // If n>0, at least one interface was not found in 'c1'.
}
/**
* Returns {@code true} if the given {@code type} is a floating point type.
*
* @param type The type to test (may be {@code null}).
* @return {@code true} if {@code type} is the primitive or wrapper class of
* {@link Float} or {@link Double}.
*/
public static boolean isFloat(final Class<?> type) {
final Classes mapping = MAPPING.get(type);
return (mapping != null) && mapping.isFloat;
}
/**
* Returns {@code true} if the given {@code type} is an integer type.
*
* @param type The type to test (may be {@code null}).
* @return {@code true} if {@code type} is the primitive of wrapper class of
* {@link Long}, {@link Integer}, {@link Short} or {@link Byte}.
*/
public static boolean isInteger(final Class<?> type) {
final Classes mapping = MAPPING.get(type);
return (mapping != null) && mapping.isInteger;
}
/**
* Returns the number of bits used by number of the specified type.
*
* @param type The type (may be {@code null}).
* @return The number of bits, or 0 if unknow.
*/
public static int getBitCount(final Class<?> type) {
final Classes mapping = MAPPING.get(type);
return (mapping != null) ? mapping.size : 0;
}
/**
* Changes a primitive class to its wrapper (e.g. {@code int} to {@link Integer}).
* If the specified class is not a primitive type, then it is returned unchanged.
*
* @param type The primitive type (may be {@code null}).
* @return The type as a wrapper.
*/
public static Class<?> primitiveToWrapper(final Class<?> type) {
final Classes mapping = MAPPING.get(type);
return (mapping != null) ? mapping.wrapper : type;
}
/**
* Changes a wrapper class to its primitive (e.g. {@link Integer} to {@code int}).
* If the specified class is not a wrapper type, then it is returned unchanged.
*
* @param type The wrapper type (may be {@code null}).
* @return The type as a primitive.
*/
public static Class<?> wrapperToPrimitive(final Class<?> type) {
final Classes mapping = MAPPING.get(type);
return (mapping != null) ? mapping.primitive : type;
}
/**
* Returns one of {@link #DOUBLE}, {@link #FLOAT}, {@link #LONG}, {@link #INTEGER},
* {@link #SHORT}, {@link #BYTE}, {@link #CHARACTER}, {@link #BOOLEAN} or {@link #OTHER}
* constants for the given type. This is a commodity for usage in {@code switch} statememnts.
*
* @param type A type (usually either a primitive type or its wrapper).
* @return The constant for the given type, or {@link #OTHER} if unknow.
*/
public static byte getEnumConstant(final Class<?> type) {
final Classes mapping = MAPPING.get(type);
return (mapping != null) ? mapping.ordinal : OTHER;
}
/**
* Converts the specified string into a value object. The value object can be an instance of
* {@link Double}, {@link Float}, {@link Long}, {@link Integer}, {@link Short}, {@link Byte},
* {@link Boolean}, {@link Character} or {@link String} according the specified type. This
* method is intentionnaly restricted to primitive types, with the addition of {@code String}
* which can be though as an identity operation. Other types like {@link java.io.File} are
* not the purpose of this method.
*
* @param <T> The requested type.
* @param type The requested type.
* @param value the value to parse.
* @return The value object, or {@code null} if {@code value} was null.
* @throws IllegalArgumentException if {@code type} is not a recognized type.
* @throws NumberFormatException if {@code type} is a subclass of {@link Number} and the
* string value is not parseable as a number of the specified type.
*/
@SuppressWarnings("unchecked")
public static <T> T valueOf(final Class<T> type, final String value)
throws IllegalArgumentException, NumberFormatException
{
if (value == null) {
return null;
}
if (Double .class.equals(type)) return (T) Double .valueOf(value);
if (Float .class.equals(type)) return (T) Float .valueOf(value);
if (Long .class.equals(type)) return (T) Long .valueOf(value);
if (Integer.class.equals(type)) return (T) Integer.valueOf(value);
if (Short .class.equals(type)) return (T) Short .valueOf(value);
if (Byte .class.equals(type)) return (T) Byte .valueOf(value);
if (Boolean.class.equals(type)) return (T) Boolean.valueOf(value);
if (Character.class.equals(type)) {
/*
* If the string is empty, returns 0 which means "end of string" in C/C++
* and NULL in Unicode standard. If non-empty, take only the first char.
* This is somewhat consistent with Boolean.valueOf(...) which is quite
* lenient about the parsing as well, and throwing a NumberFormatException
* for those would not be appropriate.
*/
return (T) Character.valueOf(value.length() != 0 ? value.charAt(0) : 0);
}
if (String.class.equals(type)) {
return (T) value;
}
throw new IllegalArgumentException(Errors.format(ErrorKeys.UNKNOW_TYPE_$1, type));
}
/**
* Returns a short class name for the specified class. This method will
* omit the package name. For example, it will return "String" instead
* of "java.lang.String" for a {@link String} object. It will also name
* array according Java language usage, for example "double[]" instead
* of "[D".
*
* @param classe The object class (may be {@code null}).
* @return A short class name for the specified object.
*/
public static String getShortName(Class<?> classe) {
if (classe == null) {
return "<*>";
}
String name = classe.getSimpleName();
Class<?> enclosing = classe.getEnclosingClass();
if (enclosing != null) {
final StringBuilder buffer = new StringBuilder();
do {
buffer.insert(0, '.').insert(0, enclosing.getSimpleName());
} while ((enclosing = enclosing.getEnclosingClass()) != null);
name = buffer.append(name).toString();
}
return name;
}
/**
* Returns a short class name for the specified object. This method will
* omit the package name. For example, it will return "String" instead
* of "java.lang.String" for a {@link String} object.
*
* @param object The object (may be {@code null}).
* @return A short class name for the specified object.
*/
public static String getShortClassName(final Object object) {
return getShortName(getClass(object));
}
}