/* * 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.Date; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; /** * A central place to register transformations between an arbitrary class and a * {@link Number}. For example, it is sometime convenient to consider {@link Date} * objects as if they were {@link Long} objects for computation purpose in generic * algorithms. Client can call the following method to convert an arbitrary object * to a {@link Number}: * * <blockquote><pre> * Object someArbitraryObject = new Date(); * Number myObjectAsANumber = {@link ClassChanger#toNumber ClassChanger.toNumber}(someArbitraryObject); * </pre></blockquote> * * @since 2.0 * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) */ public abstract class ClassChanger<S extends Comparable<S>, T extends Number> { /** * Wrapper classes sorted by their wide. */ @SuppressWarnings("unchecked") private static final Class<? extends Number>[] TYPES_BY_SIZE = new Class[] { Byte .class, Short .class, Integer.class, Long .class, Float .class, Double .class }; /** * A list of class objects that can be converted to numbers. This list is initialized * to a few commons {@link ClassChanger} instances for some standard Java classes like * {@link Date}. More objects can be added dynamically. This list must be <u>ordered</u>: * subclasses must be listed before parent classes. */ private static ClassChanger<?,?>[] changers = new ClassChanger[] { new ClassChanger<Date,Long>(Date.class, Long.class) { protected Long convert(final Date object) { return object.getTime(); } protected Date inverseConvert(final Long value) { return new Date(value.longValue()); } } }; /** * Parent class for {@link #convert}'s input objects. */ private final Class<S> source; /** * Parent class for {@link #convert}'s output objects. */ private final Class<T> target; /** * Constructs a new class changer. * * @param source Parent class for {@link #convert}'s input objects. * @param target Parent class for {@link #convert}'s output objects. */ protected ClassChanger(final Class<S> source, final Class<T> target) { this.source = source; this.target = target; if (!Comparable.class.isAssignableFrom(source)) { throw new IllegalArgumentException(String.valueOf(source)); } if (!Number.class.isAssignableFrom(target)) { throw new IllegalArgumentException(String.valueOf(target)); } } /** * Returns the numerical value for an object. * * @param object Object to convert (may be null). * @return The object's numerical value. * @throws ClassCastException if {@code object} is not of the expected class. */ protected abstract T convert(final S object) throws ClassCastException; /** * Returns an instance of the converted classe from a numerical value. * * @param value The value to wrap. * @return An instance of the source classe. */ protected abstract S inverseConvert(final T value); /** * Returns a string representation for this class changer. */ @Override public String toString() { return "ClassChanger[" + source.getName() + "\u00A0\u21E8\u00A0" + target.getName() + ']'; } /** * Registers a new converter. All registered {@link ClassChanger} will * be taken in account by the {@link #toNumber} method. The example below * register a conversion for the {@link Date} class: * * <blockquote><pre> *  ClassChanger.register(new ClassChanger(Date.class, Long.class) { *   protected Long convert(final Comparable o) { *   return ((Date) o).getTime(); *   } *   *   protected Comparable inverseConvert(final Number number) { *   return new Date(number.longValue()); *   } *  }); * </pre></blockquote> * * @param converter The {@link ClassChanger} to add. * @throws IllegalStateException if an other {@link ClassChanger} was already * registered for the same {@code source} class. This is usually * not a concern since the registration usually take place during the * class initialization ("static" constructor). */ public static synchronized void register(final ClassChanger<?,?> converter) throws IllegalStateException { int i; for (i=0; i<changers.length; i++) { if (changers[i].source.isAssignableFrom(converter.source)) { /* * We found a converter for a parent class. The new converter should be * inserted before its parent. But before the insertion, we will check * if this converter was not already registered later in the array. */ for (int j=i; j<changers.length; j++) { if (changers[j].source.equals(converter.source)) { throw new IllegalStateException(changers[j].toString()); } } break; } } changers = XArray.insert(changers, i, 1); changers[i] = converter; } /** * Returns the class changer for the specified classe. * * @param source The class. * @return The class changer for the specified class. * @throws ClassNotFoundException if {@code source} is not a registered class. */ private static synchronized <S extends Comparable<S>> ClassChanger<S,?> getClassChanger(final Class<S> source) throws ClassNotFoundException { for (int i=0; i<changers.length; i++) { final ClassChanger<?,?> candidate = changers[i]; if (candidate.source.isAssignableFrom(source)) { @SuppressWarnings("unchecked") final ClassChanger<S,?> c = (ClassChanger<S,?>) candidate; return c; } } throw new ClassNotFoundException(source.getName()); } /** * Returns the target class for the specified source class, if a suitable * transformation is known. The source class is a {@link Comparable} subclass * that will be specified as input to {@link #convert}. The target class is a * {@link Number} subclass that will be returned as output by {@link #convert}. * If no suitable mapping is found, then {@code source} is returned. */ public static synchronized Class<?> getTransformedClass(final Class<?> source) { if (source != null) { for (int i=0; i<changers.length; i++) { if (changers[i].source.isAssignableFrom(source)) { return changers[i].target; } } } return source; } /** * Returns the numeric value for the specified object. For example the code * <code>toNumber(new Date())</code> returns the {@link Date#getTime()} * value of the specified date object as a {@link Long}. * * @param object Object to convert (may be null). * @return {@code null} if {@code object} was null; otherwise * {@code object} if the supplied object is already an instance * of {@link Number}; otherwise a new number with the numerical value. * @throws ClassNotFoundException if {@code object} is not an instance of a registered class. */ @SuppressWarnings("unchecked") public static Number toNumber(final Comparable<?> object) throws ClassNotFoundException { if (object != null) { if (object instanceof Number) { return (Number) object; } ClassChanger changer = getClassChanger(object.getClass()); return changer.convert(object); } return null; } /** * Wraps the specified number as an instance of the specified classe. * For example <code>toComparable(new Long(time), Date.class)</code> * is equivalent to <code>new Date(time)</code>. There is of course no * point to use this method if the destination class is know at compile time. * This method is useful for creating instance of classes choosen dynamically * at run time. * * @param value The numerical value (may be null). * @param classe The desired classe for return value. * @throws ClassNotFoundException if {@code classe} is not a registered class. */ @SuppressWarnings("unchecked") public static <C extends Comparable> C toComparable(final Number value, final Class<C> classe) throws ClassNotFoundException { if (value != null) { if (Number.class.isAssignableFrom(classe)) { return classe.cast(value); } ClassChanger changer = getClassChanger(classe); final Comparable<?> c = changer.inverseConvert(value); return classe.cast(c); } return null; } /** * Converts a wrapper class to a primitive class. For example this method converts * <code>{@linkplain Double}.class</code> to <code>Double.{@linkplain Double#TYPE TYPE}</code>. * * @param c The wrapper class. * @return The primitive class. * @throws IllegalArgumentException if the specified class is not a wrapper for a primitive. */ public static Class<?> toPrimitive(final Class<?> c) throws IllegalArgumentException { if (Double .class.equals(c)) return Double .TYPE; if (Float .class.equals(c)) return Float .TYPE; if (Long .class.equals(c)) return Long .TYPE; if (Integer .class.equals(c)) return Integer .TYPE; if (Short .class.equals(c)) return Short .TYPE; if (Byte .class.equals(c)) return Byte .TYPE; if (Boolean .class.equals(c)) return Boolean .TYPE; if (Character.class.equals(c)) return Character.TYPE; throw unknownType(c); } /** * Converts a primitive class to a wrapper class. For example this method converts * <code>Double.{@linkplain Double#TYPE TYPE}</code> to <code>{@linkplain Double}.class</code>. * * @param c The primitive class. * @return The wrapper class. * @throws IllegalArgumentException if the specified class is not a primitive. */ public static Class<?> toWrapper(final Class<?> c) throws IllegalArgumentException { if (Double .TYPE.equals(c)) return Double .class; if (Float .TYPE.equals(c)) return Float .class; if (Long .TYPE.equals(c)) return Long .class; if (Integer .TYPE.equals(c)) return Integer .class; if (Short .TYPE.equals(c)) return Short .class; if (Byte .TYPE.equals(c)) return Byte .class; if (Boolean .TYPE.equals(c)) return Boolean .class; if (Character.TYPE.equals(c)) return Character.class; throw unknownType(c); } /** * Casts the number to the specified class. The class must by one of {@link Byte}, * {@link Short}, {@link Integer}, {@link Long}, {@link Float} or {@link Double}. */ @SuppressWarnings("unchecked") public static <N extends Number> N cast(final Number n, final Class<N> c) throws IllegalArgumentException { if (n==null || n.getClass().equals(c)) { return (N) n; } if (Byte .class.equals(c)) return (N) Byte .valueOf(n. byteValue()); if (Short .class.equals(c)) return (N) Short .valueOf(n. shortValue()); if (Integer.class.equals(c)) return (N) Integer.valueOf(n. intValue()); if (Long .class.equals(c)) return (N) Long .valueOf(n. longValue()); if (Float .class.equals(c)) return (N) Float .valueOf(n. floatValue()); if (Double .class.equals(c)) return (N) Double .valueOf(n.doubleValue()); throw unknownType(c); } /** * Returns the class of the widest type. Numbers {@code n1} and {@code n2} * must be instance of any of {@link Byte}, {@link Short}, {@link Integer}, {@link Long}, * {@link Float} or {@link Double} types. At most one of the argument can be null. */ public static Class<? extends Number> getWidestClass(final Number n1, final Number n2) throws IllegalArgumentException { return getWidestClass((n1!=null) ? n1.getClass() : null, (n2!=null) ? n2.getClass() : null); } /** * Returns the class of the widest type. Classes {@code c1} and {@code c2} * must be of any of {@link Byte}, {@link Short}, {@link Integer}, {@link Long}, * {@link Float} or {@link Double} types. At most one of the argument can be null. */ public static Class<? extends Number> getWidestClass(final Class<? extends Number> c1, final Class<? extends Number> c2) throws IllegalArgumentException { if (c1 == null) return c2; if (c2 == null) return c1; return TYPES_BY_SIZE[Math.max(getRank(c1), getRank(c2))]; } /** * Returns the class of the finest type. Classes {@code c1} and {@code c2} * must be of any of {@link Byte}, {@link Short}, {@link Integer}, {@link Long}, * {@link Float} or {@link Double} types. At most one of the argument can be null. */ public static Class<? extends Number> getFinestClass(final Class<? extends Number> c1, final Class<? extends Number> c2) throws IllegalArgumentException { if (c1 == null) return c2; if (c2 == null) return c1; return TYPES_BY_SIZE[Math.min(getRank(c1), getRank(c2))]; } /** * Returns the smallest class capable to hold the specified value. */ public static Class<? extends Number> getFinestClass(final double value) { final long lg = (long) value; if (value == lg) { if (lg >= Byte.MIN_VALUE && lg <= Byte.MAX_VALUE) return Byte.class; if (lg >= Short.MIN_VALUE && lg <= Short.MAX_VALUE) return Short.class; if (lg >= Integer.MIN_VALUE && lg <= Integer.MAX_VALUE) return Integer.class; if (lg >= Long.MIN_VALUE && lg <= Long.MAX_VALUE) return Long.class; } final float fv = (float) value; if (value == (double)fv) { return Float.class; } return Double.class; } /** * Returns the rank (in the {@link #TYPES_BY_SIZE} array) of the specified class. */ private static int getRank(final Class<? extends Number> c) throws IllegalArgumentException { for (int i=0; i<TYPES_BY_SIZE.length; i++) { if (TYPES_BY_SIZE[i].isAssignableFrom(c)) { return i; } } throw unknownType(c); } /** * Returns an exception for an unnkown type. */ private static IllegalArgumentException unknownType(final Class<?> type) { return new IllegalArgumentException(Errors.format(ErrorKeys.UNKNOW_TYPE_$1, type)); } }