KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > mckoi > database > Caster


1 /**
2  * com.mckoi.database.Caster 25 Oct 2002
3  *
4  * Mckoi SQL Database ( http://www.mckoi.com/database )
5  * Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * Version 2 as published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License Version 2 for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * Version 2 along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  *
20  * Change Log:
21  *
22  *
23  */

24
25 package com.mckoi.database;
26
27 import com.mckoi.database.global.StringObject;
28 import com.mckoi.database.global.ByteLongObject;
29 import com.mckoi.database.global.ObjectTranslator;
30 import com.mckoi.database.global.SQLTypes;
31 import com.mckoi.util.BigNumber;
32
33 import java.lang.reflect.Constructor JavaDoc;
34
35 /**
36  * Methods to choose and perform casts from database type to Java types.
37  *
38  * @author Jim McBeath
39  */

40  
41 public class Caster {
42
43   /** The cost to cast to the closest Java primitive type. */
44   public final static int PRIMITIVE_COST = 100;
45
46   /** The cost to cast to the closes Java object type. */
47   public final static int OBJECT_COST = 200;
48
49   /** The maximum positive byte value as a BigNumber. */
50   private final static BigNumber maxBigNumByte =
51                                     BigNumber.fromInt(Byte.MAX_VALUE);
52
53   /** The maximum positive byte value as a BigNumber. */
54   private final static BigNumber minBigNumByte =
55                                     BigNumber.fromInt(Byte.MIN_VALUE);
56
57   /** The maximum positive short value as a BigNumber. */
58   private final static BigNumber maxBigNumShort =
59                                     BigNumber.fromInt(Short.MAX_VALUE);
60
61   /** The maximum positive short value as a BigNumber. */
62   private final static BigNumber minBigNumShort =
63                                     BigNumber.fromInt(Short.MIN_VALUE);
64
65   /** The maximum positive integer value as a BigNumber. */
66   private final static BigNumber maxBigNumInt =
67                                     BigNumber.fromInt(Integer.MAX_VALUE);
68
69   /** The maximum positive integer value as a BigNumber. */
70   private final static BigNumber minBigNumInt =
71                                     BigNumber.fromInt(Integer.MIN_VALUE);
72
73   /** The maximum positive long value as a BigNumber. */
74   private final static BigNumber maxBigNumLong =
75                                     BigNumber.fromLong(Long.MAX_VALUE);
76
77   /** The maximum positive long value as a BigNumber. */
78   private final static BigNumber minBigNumLong =
79                                     BigNumber.fromLong(Long.MIN_VALUE);
80
81   /** The maximum positive float value as a BigNumber. */
82   private final static BigNumber maxBigNumFloat =
83                 BigNumber.fromDouble(Float.MAX_VALUE);
84
85   /** The minimum positive float value as a BigNumber. */
86   private static BigNumber minBigNumFloat =
87                 BigNumber.fromDouble(Float.MIN_VALUE);
88
89   /** The maximum positive double value as a BigNumber. */
90   private static BigNumber maxBigNumDouble =
91                 BigNumber.fromDouble(Double.MAX_VALUE);
92
93   /**
94    * Find any JAVA_OBJECTs in the args and deserialize them into
95    * real Java objects.
96    *
97    * @param args The args to deserialize. Any JAVA_OBJECT args are
98    * converted in-place to a new TObject with a value which is
99    * the deserialized object.
100    */

101   public static void deserializeJavaObjects(TObject[] args) {
102     for (int i = 0; i < args.length; i++) {
103       int sqlType = args[i].getTType().getSQLType();
104       if (sqlType != SQLTypes.JAVA_OBJECT) {
105         continue; // not a JAVA_OBJECT
106
}
107       Object JavaDoc argVal = args[i].getObject();
108       if (!(argVal instanceof ByteLongObject)) {
109         continue; // not ByteLongObject, we don't know how to deserialize
110
}
111       Object JavaDoc javaObj = ObjectTranslator.deserialize((ByteLongObject)argVal);
112       args[i] = new TObject(args[i].getTType(), javaObj);
113     }
114   }
115
116   /**
117    * Search for the best constructor that we can use with the given
118    * argument types.
119    *
120    * @param constructs The set of constructors from which to select.
121    * @param argSqlTypes The SQL types of the database arguments to be passed
122    * to the constructor.
123    * @return The constructor with the lowest cost, or null if there
124    * are no constructors that match the args.
125    */

126   public static Constructor JavaDoc findBestConstructor(
127         Constructor JavaDoc[] constructs, TObject[] args) {
128     int bestCost = 0; // not used if bestConstructor is null
129
Constructor JavaDoc bestConstructor = null;
130     int[] argSqlTypes = getSqlTypes(args);
131     for (int i = 0; i < constructs.length; ++i) {
132       Class JavaDoc[] targets = constructs[i].getParameterTypes();
133       int cost = getCastingCost(args, argSqlTypes, targets);
134       if (cost < 0) {
135     continue; // not a usable constructor
136
}
137       if (bestConstructor == null || cost < bestCost) {
138     bestCost = cost; // found a better one, remember it
139
bestConstructor = constructs[i];
140       }
141     }
142     return bestConstructor; // null if we didn't find any
143
}
144
145   /**
146    * Get the SQL types for the given database arguments.
147    *
148    * @param args The database args.
149    * @return The SQL types of the args.
150    */

151   public static int[] getSqlTypes(TObject[] args) {
152     int[] sqlTypes = new int[args.length];
153     for (int i = 0; i < args.length; i++) {
154       sqlTypes[i] = getSqlType(args[i]);
155     }
156     return sqlTypes;
157   }
158
159   /**
160    * Get the SQL type for a database argument.
161    * If the actual value does not fit into the declared type, the returned
162    * type is widened as required for the value to fit.
163    *
164    * @param arg The database argument.
165    * @return The SQL type of the arg.
166    */

167   public static int getSqlType(TObject arg) {
168     int sqlType = arg.getTType().getSQLType();
169     Object JavaDoc argVal = arg.getObject();
170     if (!(argVal instanceof BigNumber)) {
171       return sqlType; // We have special checks only for numeric values
172
}
173     BigNumber b = (BigNumber)argVal;
174     BigNumber bAbs;
175     switch (sqlType) {
176       case SQLTypes.NUMERIC:
177       case SQLTypes.DECIMAL:
178     // If the type is NUMERIC or DECIMAL, then look at the data value
179
// to see if it can be narrowed to int, long or double.
180
if (b.canBeRepresentedAsInt()) {
181       sqlType = SQLTypes.INTEGER;
182     }
183     else if (b.canBeRepresentedAsLong()) {
184       sqlType = SQLTypes.BIGINT;
185     }
186     else {
187       bAbs = b.abs();
188       if (b.getScale() == 0) {
189         if (bAbs.compareTo(maxBigNumInt) <= 0) {
190           sqlType = SQLTypes.INTEGER;
191         }
192         else if (bAbs.compareTo(maxBigNumLong) <= 0) {
193           sqlType = SQLTypes.BIGINT;
194         }
195       }
196       else if (bAbs.compareTo(maxBigNumDouble) <= 0) {
197         sqlType = SQLTypes.DOUBLE;
198       }
199     }
200     // If we can't translate NUMERIC or DECIMAL to int, long or double,
201
// then leave it as is.
202
break;
203       case SQLTypes.BIT:
204     if (b.canBeRepresentedAsInt()) {
205       int n = b.intValue();
206       if (n == 0 || n == 1) {
207         return sqlType; // Allowable BIT value
208
}
209     }
210     // The value does not fit in a BIT, move up to a TINYINT
211
sqlType = SQLTypes.TINYINT;
212     // FALL THROUGH
213
case SQLTypes.TINYINT:
214         if (b.compareTo(maxBigNumByte) <= 0 &&
215         b.compareTo(minBigNumByte) >=0 ) {
216       return sqlType; // Fits in a TINYINT
217
}
218     // The value does not fit in a TINYINT, move up to a SMALLINT
219
sqlType = SQLTypes.SMALLINT;
220     // FALL THROUGH
221
case SQLTypes.SMALLINT:
222         if (b.compareTo(maxBigNumShort) <= 0 &&
223         b.compareTo(minBigNumShort) >= 0) {
224       return sqlType; // Fits in a SMALLINT
225
}
226     // The value does not fit in a SMALLINT, move up to a INTEGER
227
sqlType = SQLTypes.INTEGER;
228     // FALL THROUGH
229
case SQLTypes.INTEGER:
230         if (b.compareTo(maxBigNumInt) <= 0 &&
231         b.compareTo(minBigNumInt) >= 0) {
232       return sqlType; // Fits in a INTEGER
233
}
234     // The value does not fit in a INTEGER, move up to a BIGINT
235
sqlType = SQLTypes.BIGINT;
236     // That's as far as we go
237
break;
238       case SQLTypes.REAL:
239     bAbs = b.abs();
240         if (bAbs.compareTo(maxBigNumFloat) <= 0 &&
241         (bAbs.compareTo(minBigNumFloat) >= 0 ||
242          b.doubleValue() == 0.0)) {
243       return sqlType; // Fits in a REAL
244
}
245     // The value does not fit in a REAL, move up to a DOUBLE
246
sqlType = SQLTypes.DOUBLE;
247     break;
248       default:
249         break;
250     }
251     return sqlType;
252   }
253
254   /**
255    * Get a string giving the database types of all of the arguments.
256    * Useful for error messages.
257    *
258    * @param args The arguments.
259    * @return A string with the types of all of the arguments,
260    * using comma as a separator.
261    */

262   public static String JavaDoc getArgTypesString(TObject[] args) {
263     StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
264     for (int n = 0; n < args.length; n++) {
265       if (n > 0) {
266     sb.append(",");
267       }
268       if (args[n] == null) {
269     sb.append("null");
270       }
271       else {
272     int sqlType = getSqlType(args[n]);
273     String JavaDoc typeName;
274     if (sqlType == SQLTypes.JAVA_OBJECT) {
275       Object JavaDoc argObj = args[n].getObject();
276       if (argObj == null) {
277         typeName = "null";
278       }
279       else {
280         typeName = argObj.getClass().getName();
281       }
282     }
283     else {
284       typeName = DataTableColumnDef.sqlTypeToString(sqlType);
285     }
286     sb.append(typeName);
287       }
288     }
289     return sb.toString();
290   }
291
292   /**
293    * Get the cost for casting the given arg types
294    * to the desired target classes.
295    *
296    * @param args The database arguments from which we are casting.
297    * @param argSqlTypes The SQL types of the args.
298    * @param targets The java classes to which we are casting.
299    * @return The cost of doing the cast for all arguments,
300    * or -1 if the args can not be cast to the targets.
301    */

302   static int getCastingCost(TObject[] args, int[] argSqlTypes,
303                             Class JavaDoc[] targets) {
304     if (targets.length != argSqlTypes.length) {
305       return -1; // wrong number of args
306
}
307
308     // Sum up the cost of converting each arg
309
int totalCost = 0;
310     for (int n = 0; n < argSqlTypes.length; ++n) {
311       int argCost = getCastingCost(args[n], argSqlTypes[n], targets[n]);
312       if (argCost < 0) {
313     return -1; //can't cast this arg type
314
}
315       int positionalCost = argCost * n / 10000;
316           //Add a little bit to disambiguate constructors based on
317
//argument position. This gives preference to earlier
318
//argument in cases where the cost of two sets of
319
//targets for the same set of args would otherwise
320
//be the same.
321
totalCost += argCost + positionalCost;
322     }
323     return totalCost;
324   }
325
326   // These arrays are used in the getCastingCost method below.
327
private static String JavaDoc[] bitPrims = { "boolean" };
328   private static Class JavaDoc[] bitClasses = { Boolean JavaDoc.class };
329
330   private static String JavaDoc[] tinyPrims = { "byte", "short", "int", "long" };
331   private static Class JavaDoc[] tinyClasses = { Byte JavaDoc.class, Short JavaDoc.class,
332             Integer JavaDoc.class, Long JavaDoc.class, Number JavaDoc.class };
333
334   private static String JavaDoc[] smallPrims = { "short", "int", "long" };
335   private static Class JavaDoc[] smallClasses = { Short JavaDoc.class, Integer JavaDoc.class,
336             Long JavaDoc.class, Number JavaDoc.class };
337
338   private static String JavaDoc[] intPrims = { "int", "long" };
339   private static Class JavaDoc[] intClasses = { Integer JavaDoc.class, Long JavaDoc.class,
340             Number JavaDoc.class };
341
342   private static String JavaDoc[] bigPrims = { "long" };
343   private static Class JavaDoc[] bigClasses = { Long JavaDoc.class, Number JavaDoc.class };
344
345   private static String JavaDoc[] floatPrims = { "float", "double" };
346   private static Class JavaDoc[] floatClasses = { Float JavaDoc.class, Double JavaDoc.class,
347             Number JavaDoc.class };
348
349   private static String JavaDoc[] doublePrims = { "double" };
350   private static Class JavaDoc[] doubleClasses = { Double JavaDoc.class, Number JavaDoc.class };
351
352   private static String JavaDoc[] stringPrims = { };
353   private static Class JavaDoc[] stringClasses = { String JavaDoc.class };
354
355   private static String JavaDoc[] datePrims = { };
356   private static Class JavaDoc[] dateClasses = { java.sql.Date JavaDoc.class,
357             java.util.Date JavaDoc.class };
358
359   private static String JavaDoc[] timePrims = { };
360   private static Class JavaDoc[] timeClasses = { java.sql.Time JavaDoc.class,
361             java.util.Date JavaDoc.class };
362
363   private static String JavaDoc[] timestampPrims = { };
364   private static Class JavaDoc[] timestampClasses = { java.sql.Timestamp JavaDoc.class,
365             java.util.Date JavaDoc.class };
366
367   /**
368    * Get the cost to cast an SQL type to the desired target class.
369    * The cost is 0 to cast to TObject,
370    * 100 to cast to the closest primitive,
371    * or 200 to cast to the closest Object,
372    * plus 1 for each widening away from the closest.
373    *
374    * @param arg The argument to cast.
375    * @param argSqlType The SQL type of the arg.
376    * @param target The target to which to cast.
377    * @return The cost to do the cast, or -1 if the cast can not be done.
378    */

379   static int getCastingCost(TObject arg, int argSqlType, Class JavaDoc target) {
380
381     //If the user has a method that takes a TObject, assume he can handle
382
//anything.
383
if (target == TObject.class) {
384       return 0;
385     }
386
387     switch (argSqlType) {
388
389       case SQLTypes.BIT:
390         return getCastingCost(arg, bitPrims, bitClasses, target);
391
392       case SQLTypes.TINYINT:
393         return getCastingCost(arg, tinyPrims, tinyClasses, target);
394
395       case SQLTypes.SMALLINT:
396         return getCastingCost(arg, smallPrims, smallClasses, target);
397
398       case SQLTypes.INTEGER:
399         return getCastingCost(arg, intPrims, intClasses, target);
400
401       case SQLTypes.BIGINT:
402         return getCastingCost(arg, bigPrims, bigClasses, target);
403
404       case SQLTypes.REAL:
405         return getCastingCost(arg, floatPrims, floatClasses, target);
406
407       case SQLTypes.FLOAT:
408       case SQLTypes.DOUBLE:
409         return getCastingCost(arg, doublePrims, doubleClasses, target);
410
411       // We only get a NUMERIC or DECIMAL type here if we were not able to
412
// convert it to int, long or double, so we can't handle it. For now we
413
// require that these types be handled by a method that takes a TObject.
414
// That gets checked at the top of this method, so if we get to here
415
// the target is not a TOBject, so we don't know how to handle it.
416
case SQLTypes.NUMERIC:
417       case SQLTypes.DECIMAL:
418         return -1;
419
420       case SQLTypes.CHAR:
421       case SQLTypes.VARCHAR:
422       case SQLTypes.LONGVARCHAR:
423         return getCastingCost(arg, stringPrims, stringClasses, target);
424
425       case SQLTypes.DATE:
426         return getCastingCost(arg, datePrims, dateClasses, target);
427
428       case SQLTypes.TIME:
429         return getCastingCost(arg, timePrims, timeClasses, target);
430
431       case SQLTypes.TIMESTAMP:
432         return getCastingCost(arg, timestampPrims, timestampClasses, target);
433
434       case SQLTypes.BINARY:
435       case SQLTypes.VARBINARY:
436       case SQLTypes.LONGVARBINARY:
437         return -1; // Can't handle these, user must use TObject
438

439       // We can cast a JAVA_OBJECT only if the value is a subtype of the
440
// target class.
441
case SQLTypes.JAVA_OBJECT:
442     Object JavaDoc argVal = arg.getObject();
443         if (argVal == null || target.isAssignableFrom(argVal.getClass())) {
444       return OBJECT_COST;
445     }
446     return -1;
447
448       // If the declared data type is NULL, then we have no type info to
449
// determine how to cast it.
450
case SQLTypes.NULL:
451         return -1;
452
453       default:
454         return -1; // Don't know how to cast other types
455
}
456   }
457
458   /**
459    * Get the cost to cast to the specified target from the set of
460    * allowable primitives and object classes.
461    *
462    * @param arg The value being cast.
463    * @param prims The set of allowable Java primitive types to which we can
464    * cast, ordered with the preferred types first.
465    * If the value of the arg is null, it can not be cast to a
466    * primitive type.
467    * @param objects The set of allowable Java Object types to which we can
468    * cast, ordered with the preferred types first.
469    * @param target The target class to which we are casting.
470    * @return The cost of the cast, or -1 if the cast is not allowed.
471    */

472   static int getCastingCost(TObject arg, String JavaDoc[] prims, Class JavaDoc[] objects,
473                             Class JavaDoc target) {
474     if (target.isPrimitive()) {
475       Object JavaDoc argVal = arg.getObject(); // get the vaue of the arg
476
if (argVal == null) {
477         return -1; // can't cast null to a primitive
478
}
479       String JavaDoc targetName = target.getName();
480       // Look for the closest allowable primitive
481
for (int i = 0; i < prims.length; i++) {
482         if (targetName.equals(prims[i]))
483       return PRIMITIVE_COST+i;
484         // Cost of casting to a primitive plus the widening cost (i)
485
}
486     } else {
487       // Look for the closest allowable object class
488
for (int i = 0; i < objects.length; i++) {
489         if (objects[i].isAssignableFrom(target))
490       return OBJECT_COST+i;
491         // Cost of casting to an object class plus the widening cost (i)
492
}
493     }
494     return -1; // can't cast it
495
}
496
497   /**
498    * Cast the given arguments to the specified constructors parameter types.
499    * The caller must already have checked to make sure the argument count
500    * and types match the constructor.
501    *
502    * @param args The database arguments from which to cast.
503    * @param constructor The constructor to which to cast.
504    * @return The cast arguments.
505    */

506   public static Object JavaDoc[] castArgsToConstructor(
507                                    TObject[] args, Constructor JavaDoc constructor) {
508     Class JavaDoc[] targets = constructor.getParameterTypes();
509     return castArgs(args, targets);
510   }
511
512   /**
513    * Cast the given arguments to the specified classes.
514    * The caller must already have checked to make sure the argument count
515    * and types match the constructor.
516    *
517    * @param args The database arguments from which to cast.
518    * @param targets The java classes to which to cast.
519    * @return The cast arguments.
520    */

521   static Object JavaDoc[] castArgs(TObject[] args, Class JavaDoc[] targets) {
522     if (targets.length != args.length) {
523       // we shouldn't get this error
524
throw new RuntimeException JavaDoc("array length mismatch: arg="+args.length+
525             ", targets="+targets.length);
526     }
527     Object JavaDoc[] castedArgs = new Object JavaDoc[args.length];
528     for (int n = 0; n < args.length; ++n) {
529       castedArgs[n] = castArg(args[n], targets[n]);
530     }
531     return castedArgs;
532   }
533
534   /**
535    * Cast the object to the specified target.
536    *
537    * @param arg The database argumument from which to cast.
538    * @param target The java class to which to cast.
539    * @return The cast object.
540    */

541   static Object JavaDoc castArg(TObject arg, Class JavaDoc target) {
542     // By the time we get here, we have already run through the cost function
543
// and eliminated the casts that don't work, including not allowing a null
544
// value to be cast to a primitive type.
545

546     if (target == TObject.class) {
547       return arg;
548     }
549
550     Object JavaDoc argVal = arg.getObject();
551     if (argVal == null) {
552       // If the arg is null, then we must be casting to an Object type,
553
// so just return null.
554
return null;
555     }
556
557     //boolean isPrimitive = target.isPrimitive();
558
String JavaDoc targetName = target.getName();
559
560     if (argVal instanceof Boolean JavaDoc) {
561       //BIT
562
if (targetName.equals("boolean") ||
563             Boolean JavaDoc.class.isAssignableFrom(target)) {
564         return argVal;
565       }
566     }
567
568     else if (argVal instanceof Number JavaDoc) {
569       //TINYINT, SMALLINT, INTEGER, BIGINT,
570
//REAL, FLOAT, DOUBLE, NUMERIC, DECIMAL
571
Number JavaDoc num = (Number JavaDoc)argVal;
572       if (targetName.equals("byte") || Byte JavaDoc.class.isAssignableFrom(target)) {
573     return new Byte JavaDoc(num.byteValue());
574       }
575       if (targetName.equals("short") || Short JavaDoc.class.isAssignableFrom(target)) {
576     return new Short JavaDoc(num.shortValue());
577       }
578       if (targetName.equals("int") || Integer JavaDoc.class.isAssignableFrom(target)) {
579     return new Integer JavaDoc(num.intValue());
580       }
581       if (targetName.equals("long") || Long JavaDoc.class.isAssignableFrom(target)) {
582     return new Long JavaDoc(num.longValue());
583       }
584       if (targetName.equals("float") || Float JavaDoc.class.isAssignableFrom(target)) {
585     return new Float JavaDoc(num.floatValue());
586       }
587       if (targetName.equals("double") ||
588             Double JavaDoc.class.isAssignableFrom(target)) {
589     return new Float JavaDoc(num.doubleValue());
590       }
591     }
592
593     else if (argVal instanceof java.util.Date JavaDoc) {
594       //DATE, TIME, TIMESTAMP
595
java.util.Date JavaDoc date = (java.util.Date JavaDoc)argVal;
596       if (java.sql.Date JavaDoc.class.isAssignableFrom(target)) {
597         return new java.sql.Date JavaDoc(date.getTime());
598       }
599       if (java.sql.Time JavaDoc.class.isAssignableFrom(target)) {
600         return new java.sql.Time JavaDoc(date.getTime());
601       }
602       if (java.sql.Timestamp JavaDoc.class.isAssignableFrom(target)) {
603         return new java.sql.Timestamp JavaDoc(date.getTime());
604       }
605       if (java.util.Date JavaDoc.class.isAssignableFrom(target)) {
606         return date;
607       }
608     }
609
610     else if (argVal instanceof String JavaDoc ||
611              argVal instanceof StringObject) {
612       //CHAR, VARCHAR, LONGVARCHAR
613
String JavaDoc s = argVal.toString();
614       if (String JavaDoc.class.isAssignableFrom(target)) {
615         return s;
616       }
617     }
618
619     else if (getSqlType(arg) == SQLTypes.JAVA_OBJECT) {
620       // JAVA_OBJECT
621
if (target.isAssignableFrom(argVal.getClass())) {
622     return argVal;
623       }
624     }
625
626     else {
627       // BINARY, VARBINARY, LONGVARBINARY
628
// NULL
629
// We don't know how to handle any of these except as TObject
630
}
631
632     // Can't cast - we should not get here, since we checked for the
633
// legality of the cast when calculating the cost. However, the
634
// code to do the cost is not the same as the code to do the casting,
635
// so we may have messed up in one or the other.
636

637     throw new RuntimeException JavaDoc("Programming error: Can't cast from "+
638                     argVal.getClass().getName() + " to " + target.getName());
639   }
640
641 }
642
Popular Tags