001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.resources;
018:
019: // Standard set of Java objects.
020: import java.util.Date;
021:
022: /**
023: * A central place to register transformations between an arbitrary class and a
024: * {@link Number}. For example, it is sometime convenient to consider {@link Date}
025: * objects as if they were {@link Long} objects for computation purpose in generic
026: * algorithms. Client can call the following method to convert an arbitrary object
027: * to a {@link Number}:
028: *
029: * <blockquote><pre>
030: * Object someArbitraryObject = new Date();
031: * Number myObjectAsANumber = {@link ClassChanger#toNumber ClassChanger.toNumber}(someArbitraryObject);
032: * </pre></blockquote>
033: *
034: * @since 2.0
035: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/metadata/src/main/java/org/geotools/resources/ClassChanger.java $
036: * @version $Id: ClassChanger.java 26581 2007-08-17 18:04:30Z desruisseaux $
037: * @author Martin Desruisseaux
038: */
039: public abstract class ClassChanger {
040: /**
041: * Wrapper classes sorted by their wide.
042: */
043: private static final Class[] CLASS_RANK = { Byte.class,
044: Short.class, Integer.class, Long.class, Float.class,
045: Double.class };
046:
047: /**
048: * Liste des classes d'objets pouvant être convertis en nombre. Cette liste
049: * contiendra par défaut quelques instances de {@link ClassChanger} pour
050: * quelques classes standards du Java, telle que {@link Date}. Toutefois,
051: * d'autres objets pourront être ajoutés par la suite. Cette liste est
052: * <u>ordonnée</u>. Les classe le plus hautes dans la hierarchie (les
053: * classes parentes) doivent apparaître à la fin.
054: */
055: private static ClassChanger[] list = new ClassChanger[] { new ClassChanger(
056: Date.class, Long.class) {
057: protected Number convert(final Comparable object) {
058: return new Long(((Date) object).getTime());
059: }
060:
061: protected Comparable inverseConvert(final Number value) {
062: return new Date(value.longValue());
063: }
064: } };
065:
066: /**
067: * Parent class for {@link #convert}'s input objects.
068: */
069: private final Class source;
070:
071: /**
072: * Parent class for {@link #convert}'s output objects.
073: */
074: private final Class target;
075:
076: /**
077: * Construct a new class changer.
078: *
079: * @param source Parent class for {@link #convert}'s input objects.
080: * @param target Parent class for {@link #convert}'s output objects.
081: */
082: protected ClassChanger(final Class source, final Class target) {
083: this .source = source;
084: this .target = target;
085: if (!Comparable.class.isAssignableFrom(source)) {
086: throw new IllegalArgumentException(String.valueOf(source));
087: }
088: if (!Number.class.isAssignableFrom(target)) {
089: throw new IllegalArgumentException(String.valueOf(target));
090: }
091: }
092:
093: /**
094: * Returns the numerical value for an object.
095: *
096: * @param object Object to convert (may be null).
097: * @return The object's numerical value.
098: * @throws ClassCastException if {@code object} is not of the expected class.
099: */
100: protected abstract Number convert(final Comparable object)
101: throws ClassCastException;
102:
103: /**
104: * Returns an instance of the converted classe from a numerical value.
105: *
106: * @param value The value to wrap.
107: * @return An instance of the source classe.
108: */
109: protected abstract Comparable inverseConvert(final Number value);
110:
111: /**
112: * Returns a string representation for this class changer.
113: */
114: //@Override
115: public String toString() {
116: return "ClassChanger[" + source.getName()
117: + "\u00A0\u21E8\u00A0" + target.getName() + ']';
118: }
119:
120: /**
121: * Registers a new transformation. All registered {@link ClassChanger} will
122: * be taken in account by the {@link #toNumber} method. The example below
123: * register a transformation for the {@link Date} class:
124: *
125: * <blockquote><pre>
126: * ClassChanger.register(new ClassChanger(Date.class, Long.class) {
127: * protected Number convert(final Comparable o) {
128: * return new Long(((Date) o).getTime());
129: * }
130: *
131: * protected Comparable inverseConvert(final Number number) {
132: * return new Date(number.longValue());
133: * }
134: * });
135: * </pre></blockquote>
136: *
137: * @param converter The {@link ClassChanger} to add.
138: * @throws IllegalStateException if an other {@link ClassChanger} was already
139: * registered for the same {@code source} class. This is usually
140: * not a concern since the registration usually take place during the
141: * class initialization ("static" constructor).
142: */
143: public static synchronized void register(
144: final ClassChanger converter) throws IllegalStateException {
145: int i;
146: for (i = 0; i < list.length; i++) {
147: if (list[i].source.isAssignableFrom(converter.source)) {
148: /*
149: * On a trouvé un convertisseur qui utilisait
150: * une classe parente. Le nouveau convertisseur
151: * devra s'insérer avant son parent. Mais on va
152: * d'abord s'assurer qu'il n'existait pas déjà
153: * un convertisseur pour cette classe.
154: */
155: for (int j = i; j < list.length; j++) {
156: if (list[j].source.equals(converter.source)) {
157: throw new IllegalStateException(list[j]
158: .toString());
159: }
160: }
161: break;
162: }
163: }
164: list = (ClassChanger[]) XArray.insert(list, i, 1);
165: list[i] = converter;
166: }
167:
168: /**
169: * Returns the class changer for the specified classe.
170: *
171: * @param source The class.
172: * @return The class changer for the specified class.
173: * @throws ClassNotFoundException if {@code source} is not a registered class.
174: */
175: private static synchronized ClassChanger getClassChanger(
176: final Class source) throws ClassNotFoundException {
177: for (int i = 0; i < list.length; i++) {
178: if (list[i].source.isAssignableFrom(source)) {
179: return list[i];
180: }
181: }
182: throw new ClassNotFoundException(source.getName());
183: }
184:
185: /**
186: * Returns the target class for the specified source class, if a suitable
187: * transformation is known. The source class is a {@link Comparable} subclass
188: * that will be specified as input to {@link #convert}. The target class is a
189: * {@link Number} subclass that will be returned as output by {@link #convert}.
190: * If no suitable mapping is found, then {@code source} is returned.
191: */
192: public static synchronized Class getTransformedClass(
193: final Class source) {
194: if (source != null) {
195: for (int i = 0; i < list.length; i++) {
196: if (list[i].source.isAssignableFrom(source)) {
197: return list[i].target;
198: }
199: }
200: }
201: return source;
202: }
203:
204: /**
205: * Returns the numeric value for the specified object. For example the code
206: * <code>toNumber(new Date())</code> returns the {@link Date#getTime()}
207: * value of the specified date object as a {@link Long}.
208: *
209: * @param object Object to convert (may be null).
210: * @return {@code null} if {@code object} was null; otherwise
211: * {@code object} if the supplied object is already an instance
212: * of {@link Number}; otherwise a new number with the numerical value.
213: * @throws ClassNotFoundException if {@code object} is not an instance
214: * of a registered class.
215: */
216: public static Number toNumber(final Comparable object)
217: throws ClassNotFoundException {
218: if (object != null) {
219: if (object instanceof Number) {
220: return (Number) object;
221: }
222: return getClassChanger(object.getClass()).convert(object);
223: }
224: return null;
225: }
226:
227: /**
228: * Wraps the specified number as an instance of the specified classe.
229: * For example <code>toComparable(Date.class, new Long(time))</code>
230: * is equivalent to <code>new Date(time)</code>. There is of course no
231: * point to use this method if the destination class is know at compile time.
232: * This method is useful for creating instance of classes choosen dynamically
233: * at run time.
234: *
235: * @param value The numerical value (may be null).
236: * @param classe The desired classe for return value.
237: * @throws ClassNotFoundException if {@code classe} is not a registered class.
238: */
239: public static Comparable toComparable(final Number value,
240: final Class classe) throws ClassNotFoundException {
241: if (value != null) {
242: if (Number.class.isAssignableFrom(classe)) {
243: return (Comparable) value;
244: }
245: return getClassChanger(classe).inverseConvert(value);
246: }
247: return null;
248: }
249:
250: /**
251: * Converts a wrapper class to a primitive class. For example this method converts
252: * <code>{@linkplain Double}.class</code> to <code>Double.{@linkplain Double#TYPE TYPE}</code>.
253: *
254: * @param c The wrapper class.
255: * @return The primitive class.
256: * @throws IllegalArgumentException if the specified class is not a wrapper for a primitive.
257: */
258: public static Class toPrimitive(final Class c)
259: throws IllegalArgumentException {
260: if (Double.class.equals(c))
261: return Double.TYPE;
262: if (Float.class.equals(c))
263: return Float.TYPE;
264: if (Long.class.equals(c))
265: return Long.TYPE;
266: if (Integer.class.equals(c))
267: return Integer.TYPE;
268: if (Short.class.equals(c))
269: return Short.TYPE;
270: if (Byte.class.equals(c))
271: return Byte.TYPE;
272: if (Boolean.class.equals(c))
273: return Boolean.TYPE;
274: if (Character.class.equals(c))
275: return Character.TYPE;
276: throw new IllegalArgumentException(Utilities.getShortName(c));
277: }
278:
279: /**
280: * Converts a primitive class to a wrapper class. For example this method converts
281: * <code>Double.{@linkplain Double#TYPE TYPE}</code> to <code>{@linkplain Double}.class</code>.
282: *
283: * @param c The primitive class.
284: * @return The wrapper class.
285: * @throws IllegalArgumentException if the specified class is not a primitive.
286: */
287: public static Class toWrapper(final Class c)
288: throws IllegalArgumentException {
289: if (Double.TYPE.equals(c))
290: return Double.class;
291: if (Float.TYPE.equals(c))
292: return Float.class;
293: if (Long.TYPE.equals(c))
294: return Long.class;
295: if (Integer.TYPE.equals(c))
296: return Integer.class;
297: if (Short.TYPE.equals(c))
298: return Short.class;
299: if (Byte.TYPE.equals(c))
300: return Byte.class;
301: if (Boolean.TYPE.equals(c))
302: return Boolean.class;
303: if (Character.TYPE.equals(c))
304: return Character.class;
305: throw new IllegalArgumentException(Utilities.getShortName(c));
306: }
307:
308: /**
309: * Casts the number to the specified class. The class must by one of {@link Byte},
310: * {@link Short}, {@link Integer}, {@link Long}, {@link Float} or {@link Double}.
311: *
312: * @todo Use {@code valueOf} when we will be allowed to compile for J2SE 1.5.
313: */
314: public static Number cast(final Number n, final Class c) {
315: if (n != null && !n.getClass().equals(c)) {
316: if (Byte.class.equals(c))
317: return new Byte(n.byteValue());
318: if (Short.class.equals(c))
319: return new Short(n.shortValue());
320: if (Integer.class.equals(c))
321: return new Integer(n.intValue());
322: if (Long.class.equals(c))
323: return new Long(n.longValue());
324: if (Float.class.equals(c))
325: return new Float(n.floatValue());
326: if (Double.class.equals(c))
327: return new Double(n.doubleValue());
328: throw new IllegalArgumentException(Utilities
329: .getShortName(c));
330: }
331: return n;
332: }
333:
334: /**
335: * Returns the class of the widest type. Numbers {@code n1} and {@code n2}
336: * must be instance of any of {@link Byte}, {@link Short}, {@link Integer}, {@link Long},
337: * {@link Float} or {@link Double} types. At most one of the argument can be null.
338: */
339: public static Class getWidestClass(final Number n1, final Number n2) {
340: return getWidestClass((n1 != null) ? n1.getClass() : null,
341: (n2 != null) ? n2.getClass() : null);
342: }
343:
344: /**
345: * Returns the class of the widest type. Classes {@code c1} and {@code c2}
346: * must be of any of {@link Byte}, {@link Short}, {@link Integer}, {@link Long},
347: * {@link Float} or {@link Double} types. At most one of the argument can be null.
348: */
349: public static Class getWidestClass(final Class c1, final Class c2) {
350: if (c1 == null)
351: return c2;
352: if (c2 == null)
353: return c1;
354: return CLASS_RANK[Math.max(getRank(c1), getRank(c2))];
355: }
356:
357: /**
358: * Returns the class of the finest type. Classes {@code c1} and {@code c2}
359: * must be of any of {@link Byte}, {@link Short}, {@link Integer}, {@link Long},
360: * {@link Float} or {@link Double} types. At most one of the argument can be null.
361: */
362: public static Class getFinestClass(final Class c1, final Class c2) {
363: if (c1 == null)
364: return c2;
365: if (c2 == null)
366: return c1;
367: return CLASS_RANK[Math.min(getRank(c1), getRank(c2))];
368: }
369:
370: /**
371: * Returns the smallest class capable to hold the specified value.
372: */
373: public static Class getFinestClass(final double value) {
374: final long lg = (long) value;
375: if (value == lg) {
376: if (lg >= Byte.MIN_VALUE && lg <= Byte.MAX_VALUE)
377: return Byte.class;
378: if (lg >= Short.MIN_VALUE && lg <= Short.MAX_VALUE)
379: return Short.class;
380: if (lg >= Integer.MIN_VALUE && lg <= Integer.MAX_VALUE)
381: return Integer.class;
382: if (lg >= Short.MIN_VALUE && lg <= Long.MAX_VALUE)
383: return Long.class;
384: }
385: final float fv = (float) value;
386: if (value == (double) fv) {
387: return Float.class;
388: }
389: return Double.class;
390: }
391:
392: /**
393: * Returns the rank (in the {@link #CLASS_RANK} array) of the specified class.
394: */
395: private static int getRank(final Class c) {
396: for (int i = 0; i < CLASS_RANK.length; i++) {
397: if (CLASS_RANK[i].isAssignableFrom(c)) {
398: return i;
399: }
400: }
401: throw new IllegalArgumentException(Utilities.getShortName(c));
402: }
403: }
|