Caster.java :  » Database-DBMS » mckoi » com » mckoi » database » Java Open Source

Java Open Source » Database DBMS » mckoi 
mckoi » com » mckoi » database » Caster.java
/**
 * com.mckoi.database.Caster  25 Oct 2002
 *
 * Mckoi SQL Database ( http://www.mckoi.com/database )
 * Copyright (C) 2000, 2001, 2002  Diehl and Associates, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * Version 2 as published by the Free Software Foundation.
 *
 * This program 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 General Public License Version 2 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * Version 2 along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Change Log:
 * 
 * 
 */

package com.mckoi.database;

import com.mckoi.database.global.StringObject;
import com.mckoi.database.global.ByteLongObject;
import com.mckoi.database.global.ObjectTranslator;
import com.mckoi.database.global.SQLTypes;
import com.mckoi.util.BigNumber;

import java.lang.reflect.Constructor;

/**
 * Methods to choose and perform casts from database type to Java types.
 *
 * @author Jim McBeath
 */
 
public class Caster {

  /** The cost to cast to the closest Java primitive type. */
  public final static int PRIMITIVE_COST = 100;

  /** The cost to cast to the closes Java object type. */
  public final static int OBJECT_COST = 200;

  /** The maximum positive byte value as a BigNumber. */
  private final static BigNumber maxBigNumByte =
                                    BigNumber.fromInt(Byte.MAX_VALUE);

  /** The maximum positive byte value as a BigNumber. */
  private final static BigNumber minBigNumByte =
                                    BigNumber.fromInt(Byte.MIN_VALUE);

  /** The maximum positive short value as a BigNumber. */
  private final static BigNumber maxBigNumShort =
                                    BigNumber.fromInt(Short.MAX_VALUE);

  /** The maximum positive short value as a BigNumber. */
  private final static BigNumber minBigNumShort =
                                    BigNumber.fromInt(Short.MIN_VALUE);

  /** The maximum positive integer value as a BigNumber. */
  private final static BigNumber maxBigNumInt =
                                    BigNumber.fromInt(Integer.MAX_VALUE);

  /** The maximum positive integer value as a BigNumber. */
  private final static BigNumber minBigNumInt =
                                    BigNumber.fromInt(Integer.MIN_VALUE);

  /** The maximum positive long value as a BigNumber. */
  private final static BigNumber maxBigNumLong =
                                    BigNumber.fromLong(Long.MAX_VALUE);

  /** The maximum positive long value as a BigNumber. */
  private final static BigNumber minBigNumLong =
                                    BigNumber.fromLong(Long.MIN_VALUE);

  /** The maximum positive float value as a BigNumber. */
  private final static BigNumber maxBigNumFloat =
          BigNumber.fromDouble(Float.MAX_VALUE);

  /** The minimum positive float value as a BigNumber. */
  private static BigNumber minBigNumFloat =
          BigNumber.fromDouble(Float.MIN_VALUE);

  /** The maximum positive double value as a BigNumber. */
  private static BigNumber maxBigNumDouble =
          BigNumber.fromDouble(Double.MAX_VALUE);

  /**
   * Find any JAVA_OBJECTs in the args and deserialize them into
   * real Java objects.
   *
   * @param args The args to deserialize.  Any JAVA_OBJECT args are
   *        converted in-place to a new TObject with a value which is
   *        the deserialized object.
   */
  public static void deserializeJavaObjects(TObject[] args) {
    for (int i = 0; i < args.length; i++) {
      int sqlType = args[i].getTType().getSQLType();
      if (sqlType != SQLTypes.JAVA_OBJECT) {
        continue;  // not a JAVA_OBJECT
      }
      Object argVal = args[i].getObject();
      if (!(argVal instanceof ByteLongObject)) {
        continue;  // not ByteLongObject, we don't know how to deserialize
      }
      Object javaObj = ObjectTranslator.deserialize((ByteLongObject)argVal);
      args[i] = new TObject(args[i].getTType(), javaObj);
    }
  }

  /**
   * Search for the best constructor that we can use with the given
   * argument types.
   *
   * @param constructs The set of constructors from which to select.
   * @param argSqlTypes The SQL types of the database arguments to be passed
   *        to the constructor.
   * @return The constructor with the lowest cost, or null if there
   *         are no constructors that match the args.
   */
  public static Constructor findBestConstructor(
      Constructor[] constructs, TObject[] args) {
    int bestCost = 0;    // not used if bestConstructor is null
    Constructor bestConstructor = null;
    int[] argSqlTypes = getSqlTypes(args);
    for (int i = 0; i < constructs.length; ++i) {
      Class[] targets = constructs[i].getParameterTypes();
      int cost = getCastingCost(args, argSqlTypes, targets);
      if (cost < 0) {
  continue;    // not a usable constructor
      }
      if (bestConstructor == null || cost < bestCost) {
  bestCost = cost;  // found a better one, remember it
  bestConstructor = constructs[i];
      }
    }
    return bestConstructor;  // null if we didn't find any
  }

  /**
   * Get the SQL types for the given database arguments.
   *
   * @param args The database args.
   * @return The SQL types of the args.
   */
  public static int[] getSqlTypes(TObject[] args) {
    int[] sqlTypes = new int[args.length];
    for (int i = 0; i < args.length; i++) {
      sqlTypes[i] = getSqlType(args[i]);
    }
    return sqlTypes;
  }

  /**
   * Get the SQL type for a database argument.
   * If the actual value does not fit into the declared type, the returned
   * type is widened as required for the value to fit.
   *
   * @param arg The database argument.
   * @return The SQL type of the arg.
   */
  public static int getSqlType(TObject arg) {
    int sqlType = arg.getTType().getSQLType();
    Object argVal = arg.getObject();
    if (!(argVal instanceof BigNumber)) {
      return sqlType;  // We have special checks only for numeric values
    }
    BigNumber b = (BigNumber)argVal;
    BigNumber bAbs;
    switch (sqlType) {
      case SQLTypes.NUMERIC:
      case SQLTypes.DECIMAL:
  // If the type is NUMERIC or DECIMAL, then look at the data value
  // to see if it can be narrowed to int, long or double.
  if (b.canBeRepresentedAsInt()) {
    sqlType = SQLTypes.INTEGER;
  }
  else if (b.canBeRepresentedAsLong()) {
    sqlType = SQLTypes.BIGINT;
  }
  else {
    bAbs = b.abs();
    if (b.getScale() == 0) {
      if (bAbs.compareTo(maxBigNumInt) <= 0) {
        sqlType = SQLTypes.INTEGER;
      }
      else if (bAbs.compareTo(maxBigNumLong) <= 0) {
        sqlType = SQLTypes.BIGINT;
      }
    }
    else if (bAbs.compareTo(maxBigNumDouble) <= 0) {
      sqlType = SQLTypes.DOUBLE;
    }
  }
  // If we can't translate NUMERIC or DECIMAL to int, long or double,
  // then leave it as is.
  break;
      case SQLTypes.BIT:
  if (b.canBeRepresentedAsInt()) {
    int n = b.intValue();
    if (n == 0 || n == 1) {
      return sqlType;  // Allowable BIT value
    }
  }
  // The value does not fit in a BIT, move up to a TINYINT
  sqlType = SQLTypes.TINYINT;
  // FALL THROUGH
      case SQLTypes.TINYINT:
        if (b.compareTo(maxBigNumByte) <= 0 &&
      b.compareTo(minBigNumByte) >=0 ) {
    return sqlType;  // Fits in a TINYINT
  }
  // The value does not fit in a TINYINT, move up to a SMALLINT
  sqlType = SQLTypes.SMALLINT;
  // FALL THROUGH
      case SQLTypes.SMALLINT:
        if (b.compareTo(maxBigNumShort) <= 0 &&
      b.compareTo(minBigNumShort) >= 0) {
    return sqlType;  // Fits in a SMALLINT
  }
  // The value does not fit in a SMALLINT, move up to a INTEGER
  sqlType = SQLTypes.INTEGER;
  // FALL THROUGH
      case SQLTypes.INTEGER:
        if (b.compareTo(maxBigNumInt) <= 0 &&
      b.compareTo(minBigNumInt) >= 0) {
    return sqlType;  // Fits in a INTEGER
  }
  // The value does not fit in a INTEGER, move up to a BIGINT
  sqlType = SQLTypes.BIGINT;
  // That's as far as we go
  break;
      case SQLTypes.REAL:
  bAbs = b.abs();
        if (bAbs.compareTo(maxBigNumFloat) <= 0 &&
      (bAbs.compareTo(minBigNumFloat) >= 0 ||
       b.doubleValue() == 0.0)) {
    return sqlType;  // Fits in a REAL
  }
  // The value does not fit in a REAL, move up to a DOUBLE
  sqlType = SQLTypes.DOUBLE;
  break;
      default:
        break;
    }
    return sqlType;
  }

  /**
   * Get a string giving the database types of all of the arguments.
   * Useful for error messages.
   *
   * @param args The arguments.
   * @return A string with the types of all of the arguments,
   *         using comma as a separator.
   */
  public static String getArgTypesString(TObject[] args) {
    StringBuffer sb = new StringBuffer();
    for (int n = 0; n < args.length; n++) {
      if (n > 0) {
  sb.append(",");
      }
      if (args[n] == null) {
  sb.append("null");
      }
      else {
  int sqlType = getSqlType(args[n]);
  String typeName;
  if (sqlType == SQLTypes.JAVA_OBJECT) {
    Object argObj = args[n].getObject();
    if (argObj == null) {
      typeName = "null";
    }
    else {
      typeName = argObj.getClass().getName();
    }
  }
  else {
    typeName = DataTableColumnDef.sqlTypeToString(sqlType);
  }
  sb.append(typeName);
      }
    }
    return sb.toString();
  }

  /**
   * Get the cost for casting the given arg types
   * to the desired target classes.
   *
   * @param args The database arguments from which we are casting.
   * @param argSqlTypes The SQL types of the args.
   * @param targets The java classes to which we are casting.
   * @return The cost of doing the cast for all arguments,
   *         or -1 if the args can not be cast to the targets.
   */
  static int getCastingCost(TObject[] args, int[] argSqlTypes,
                            Class[] targets) {
    if (targets.length != argSqlTypes.length) {
      return -1;    // wrong number of args
    }

    // Sum up the cost of converting each arg
    int totalCost = 0;
    for (int n = 0; n < argSqlTypes.length; ++n) {
      int argCost = getCastingCost(args[n], argSqlTypes[n], targets[n]);
      if (argCost < 0) {
  return -1;    //can't cast this arg type
      }
      int positionalCost = argCost * n / 10000;
        //Add a little bit to disambiguate constructors based on
        //argument position.  This gives preference to earlier
        //argument in cases where the cost of two sets of
        //targets for the same set of args would otherwise
        //be the same.
      totalCost += argCost + positionalCost;
    }
    return totalCost;
  }

  // These arrays are used in the getCastingCost method below.
  private static String[] bitPrims = { "boolean" };
  private static Class[] bitClasses = { Boolean.class };

  private static String[] tinyPrims = { "byte", "short", "int", "long" };
  private static Class[] tinyClasses = { Byte.class, Short.class,
      Integer.class, Long.class, Number.class };

  private static String[] smallPrims = { "short", "int", "long" };
  private static Class[] smallClasses = { Short.class, Integer.class,
      Long.class, Number.class };

  private static String[] intPrims = { "int", "long" };
  private static Class[] intClasses = { Integer.class, Long.class,
        Number.class };

  private static String[] bigPrims = { "long" };
  private static Class[] bigClasses = { Long.class, Number.class };

  private static String[] floatPrims = { "float", "double" };
  private static Class[] floatClasses = { Float.class, Double.class,
        Number.class };

  private static String[] doublePrims = { "double" };
  private static Class[] doubleClasses = { Double.class, Number.class };

  private static String[] stringPrims = { };
  private static Class[] stringClasses = { String.class };

  private static String[] datePrims = { };
  private static Class[] dateClasses = { java.sql.Date.class,
        java.util.Date.class };

  private static String[] timePrims = { };
  private static Class[] timeClasses = { java.sql.Time.class,
        java.util.Date.class };

  private static String[] timestampPrims = { };
  private static Class[] timestampClasses = { java.sql.Timestamp.class,
      java.util.Date.class };

  /**
   * Get the cost to cast an SQL type to the desired target class.
   * The cost is 0 to cast to TObject,
   * 100 to cast to the closest primitive,
   * or 200 to cast to the closest Object,
   * plus 1 for each widening away from the closest.
   *
   * @param arg The argument to cast.
   * @param argSqlType The SQL type of the arg.
   * @param target The target to which to cast.
   * @return The cost to do the cast, or -1 if the cast can not be done.
   */
  static int getCastingCost(TObject arg, int argSqlType, Class target) {

    //If the user has a method that takes a TObject, assume he can handle
    //anything.
    if (target == TObject.class) {
      return 0;
    }

    switch (argSqlType) {

      case SQLTypes.BIT:
        return getCastingCost(arg, bitPrims, bitClasses, target);

      case SQLTypes.TINYINT:
        return getCastingCost(arg, tinyPrims, tinyClasses, target);

      case SQLTypes.SMALLINT:
        return getCastingCost(arg, smallPrims, smallClasses, target);

      case SQLTypes.INTEGER:
        return getCastingCost(arg, intPrims, intClasses, target);

      case SQLTypes.BIGINT:
        return getCastingCost(arg, bigPrims, bigClasses, target);

      case SQLTypes.REAL:
        return getCastingCost(arg, floatPrims, floatClasses, target);

      case SQLTypes.FLOAT:
      case SQLTypes.DOUBLE:
        return getCastingCost(arg, doublePrims, doubleClasses, target);

      // We only get a NUMERIC or DECIMAL type here if we were not able to
      // convert it to int, long or double, so we can't handle it.  For now we
      // require that these types be handled by a method that takes a TObject.
      // That gets checked at the top of this method, so if we get to here
      // the target is not a TOBject, so we don't know how to handle it.
      case SQLTypes.NUMERIC:
      case SQLTypes.DECIMAL:
        return -1;

      case SQLTypes.CHAR:
      case SQLTypes.VARCHAR:
      case SQLTypes.LONGVARCHAR:
        return getCastingCost(arg, stringPrims, stringClasses, target);

      case SQLTypes.DATE:
        return getCastingCost(arg, datePrims, dateClasses, target);

      case SQLTypes.TIME:
        return getCastingCost(arg, timePrims, timeClasses, target);

      case SQLTypes.TIMESTAMP:
        return getCastingCost(arg, timestampPrims, timestampClasses, target);

      case SQLTypes.BINARY:
      case SQLTypes.VARBINARY:
      case SQLTypes.LONGVARBINARY:
        return -1;  // Can't handle these, user must use TObject

      // We can cast a JAVA_OBJECT only if the value is a subtype of the
      // target class.
      case SQLTypes.JAVA_OBJECT:
  Object argVal = arg.getObject();
        if (argVal == null || target.isAssignableFrom(argVal.getClass())) {
    return OBJECT_COST;
  }
  return -1;

      // If the declared data type is NULL, then we have no type info to
      // determine how to cast it.
      case SQLTypes.NULL:
        return -1;

      default:
        return -1;  // Don't know how to cast other types
    }
  }

  /**
   * Get the cost to cast to the specified target from the set of
   * allowable primitives and object classes.
   *
   * @param arg The value being cast.
   * @param prims The set of allowable Java primitive types to which we can
   *              cast, ordered with the preferred types first.
   *              If the value of the arg is null, it can not be cast to a
   *              primitive type.
   * @param objects The set of allowable Java Object types to which we can
   *                cast, ordered with the preferred types first.
   * @param target The target class to which we are casting.
   * @return The cost of the cast, or -1 if the cast is not allowed.
   */
  static int getCastingCost(TObject arg, String[] prims, Class[] objects,
                            Class target) {
    if (target.isPrimitive()) {
      Object argVal = arg.getObject();  // get the vaue of the arg
      if (argVal == null) {
        return -1;  // can't cast null to a primitive
      }
      String targetName = target.getName();
      // Look for the closest allowable primitive
      for (int i = 0; i < prims.length; i++) {
        if (targetName.equals(prims[i]))
    return PRIMITIVE_COST+i;
      // Cost of casting to a primitive plus the widening cost (i)
      }
    } else {
      // Look for the closest allowable object class
      for (int i = 0; i < objects.length; i++) {
        if (objects[i].isAssignableFrom(target))
    return OBJECT_COST+i;
      // Cost of casting to an object class plus the widening cost (i)
      }
    }
    return -1;  // can't cast it
  }

  /**
   * Cast the given arguments to the specified constructors parameter types.
   * The caller must already have checked to make sure the argument count
   * and types match the constructor.
   *
   * @param args The database arguments from which to cast.
   * @param constructor The constructor to which to cast.
   * @return The cast arguments.
   */
  public static Object[] castArgsToConstructor(
                                   TObject[] args, Constructor constructor) {
    Class[] targets = constructor.getParameterTypes();
    return castArgs(args, targets);
  }

  /**
   * Cast the given arguments to the specified classes.
   * The caller must already have checked to make sure the argument count
   * and types match the constructor.
   *
   * @param args The database arguments from which to cast.
   * @param targets The java classes to which to cast.
   * @return The cast arguments.
   */
  static Object[] castArgs(TObject[] args, Class[] targets) {
    if (targets.length != args.length) {
      // we shouldn't get this error
      throw new RuntimeException("array length mismatch: arg="+args.length+
          ", targets="+targets.length);
    }
    Object[] castedArgs = new Object[args.length];
    for (int n = 0; n < args.length; ++n) {
      castedArgs[n] = castArg(args[n], targets[n]);
    }
    return castedArgs;
  }

  /**
   * Cast the object to the specified target.
   *
   * @param arg The database argumument from which to cast.
   * @param target The java class to which to cast.
   * @return The cast object.
   */
  static Object castArg(TObject arg, Class target) {
    // By the time we get here, we have already run through the cost function
    // and eliminated the casts that don't work, including not allowing a null
    // value to be cast to a primitive type.

    if (target == TObject.class) {
      return arg;
    } 

    Object argVal = arg.getObject();
    if (argVal == null) {
      // If the arg is null, then we must be casting to an Object type,
      // so just return null.
      return null;
    }

    //boolean isPrimitive = target.isPrimitive();
    String targetName = target.getName();

    if (argVal instanceof Boolean) {
      //BIT
      if (targetName.equals("boolean") ||
          Boolean.class.isAssignableFrom(target)) {
        return argVal;
      }
    }

    else if (argVal instanceof Number) {
      //TINYINT, SMALLINT, INTEGER, BIGINT,
      //REAL, FLOAT, DOUBLE, NUMERIC, DECIMAL
      Number num = (Number)argVal;
      if (targetName.equals("byte") || Byte.class.isAssignableFrom(target)) {
  return new Byte(num.byteValue());
      }
      if (targetName.equals("short") || Short.class.isAssignableFrom(target)) {
  return new Short(num.shortValue());
      }
      if (targetName.equals("int") || Integer.class.isAssignableFrom(target)) {
  return new Integer(num.intValue());
      }
      if (targetName.equals("long") || Long.class.isAssignableFrom(target)) {
  return new Long(num.longValue());
      }
      if (targetName.equals("float") || Float.class.isAssignableFrom(target)) {
  return new Float(num.floatValue());
      }
      if (targetName.equals("double") ||
          Double.class.isAssignableFrom(target)) {
  return new Float(num.doubleValue());
      }
    }

    else if (argVal instanceof java.util.Date) {
      //DATE, TIME, TIMESTAMP
      java.util.Date date = (java.util.Date)argVal;
      if (java.sql.Date.class.isAssignableFrom(target)) {
        return new java.sql.Date(date.getTime());
      }
      if (java.sql.Time.class.isAssignableFrom(target)) {
        return new java.sql.Time(date.getTime());
      }
      if (java.sql.Timestamp.class.isAssignableFrom(target)) {
        return new java.sql.Timestamp(date.getTime());
      }
      if (java.util.Date.class.isAssignableFrom(target)) {
        return date;
      }
    }

    else if (argVal instanceof String ||
             argVal instanceof StringObject) {
      //CHAR, VARCHAR, LONGVARCHAR
      String s = argVal.toString();
      if (String.class.isAssignableFrom(target)) {
        return s;
      }
    }

    else if (getSqlType(arg) == SQLTypes.JAVA_OBJECT) {
      // JAVA_OBJECT
      if (target.isAssignableFrom(argVal.getClass())) {
  return argVal;
      }
    }

    else {
      // BINARY, VARBINARY, LONGVARBINARY
      // NULL
      // We don't know how to handle any of these except as TObject
    }

    // Can't cast - we should not get here, since we checked for the
    // legality of the cast when calculating the cost.  However, the
    // code to do the cost is not the same as the code to do the casting,
    // so we may have messed up in one or the other.

    throw new RuntimeException("Programming error: Can't cast from "+
                    argVal.getClass().getName() + " to " + target.getName());
  }

}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.