/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999-2004 Bull S.A.
 * Contact: jonas-team@ow2.org
 * 
 * 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; either
 * version 2.1 of the License, or any later version.
 * 
 * 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.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */


package org.ow2.jonas.lib.ejb21;

import java.util.Hashtable;

/**
 * This class implements methods which permit to get informations about Java Types:
 * <ul>
 * <li>
 * the JDBC Types associated
 * <li>
 * the ResultSet.getXXX() methods associated
 * <li>
 * the PreparedStatement.setXXX() methods associated
 * </ul>
 * <br>
 * (The Java Type may be or not a primitive type)
 * 
 * @author Helene Joanin : Initial developer
 * @author ico.Hoogervorst@nl.compuware.com : Support for BigInteger and avoid cast exceptions with rs.getObject()
 */
public class JavaType {

    // Default values for Java primitive type
    private static Hashtable tableDefaultValues = new Hashtable();
    static {
        tableDefaultValues.put(java.lang.Boolean.TYPE, "false");
        tableDefaultValues.put(java.lang.Character.TYPE, "'\u0000'");
        tableDefaultValues.put(java.lang.Byte.TYPE, "(byte)0");
        tableDefaultValues.put(java.lang.Short.TYPE, "(short)0");
        tableDefaultValues.put(java.lang.Integer.TYPE, "0");
        tableDefaultValues.put(java.lang.Long.TYPE, "0L");
        tableDefaultValues.put(java.lang.Float.TYPE, "0.0f");
        tableDefaultValues.put(java.lang.Double.TYPE, "0.0d");
    }
    
    private static Hashtable tableWrapperType = new Hashtable();
    static {
        tableWrapperType.put(java.lang.Boolean.TYPE, "java.lang.Boolean");
        tableWrapperType.put(java.lang.Character.TYPE, "java.lang.Character");
        tableWrapperType.put(java.lang.Byte.TYPE, "java.lang.Byte");
        tableWrapperType.put(java.lang.Short.TYPE, "java.lang.Short");
        tableWrapperType.put(java.lang.Integer.TYPE, "java.lang.Integer");
        tableWrapperType.put(java.lang.Long.TYPE, "java.lang.Long");
        tableWrapperType.put(java.lang.Float.TYPE, "java.lang.Float");
        tableWrapperType.put(java.lang.Double.TYPE, "java.lang.Double");
    }
    

    /**
     * Returns true if the given type is the type 'void'
     */
    public static boolean isVoid(Class c) {
        return (c.equals(java.lang.Void.TYPE));
    }

    /**
     * Returns the name of the given type
     */
    public static String getName(Class c) {
        String name;
        if (c.isArray()) {
            name = getName(c.getComponentType()) + "[]";
        } else {
            // The '$' in the name of a inner class is no more allowed since JDK 1.4.2
            name = c.getName().replace('$', '.');
        }
        return (name);
    }

    /**
     * Returns true if the given class is a Serializable Java Type,
     * false otherwise.
     */
    public static boolean isSerializable(Class c) {
        boolean isSerializable = false;
        if (c.isArray()) {
            isSerializable = isSerializable(c.getComponentType());
        } else if (c.isPrimitive()) {
            isSerializable = true;
        } else {
            isSerializable = java.io.Serializable.class.isAssignableFrom(c);
        }
        return (isSerializable);
    }

    /**
     * Returns true if the given class is a valid type for RMI,
     * false otherwise.
     * Valid types for RMI are primitive types, remote objects,
     * and non-remote objects that implement the java.io.Serializable interface
     */
    public static boolean isValidForRmi(Class c) {
        return (JavaType.isSerializable(c) || java.rmi.Remote.class.isAssignableFrom(c));
    }

    /**
     * Returns true if the given class implements java.uti.Collection or java.util.Enumeration
     * false otherwise.
     */
    public static boolean isCollecOrEnum(Class c) {
        return (java.util.Collection.class.isAssignableFrom(c) || java.util.Enumeration.class.isAssignableFrom(c));
    }

    /**
     * Returns the name of the getXXX method associated with the given type in
     * java.sql.ResultSet.
     * @param  c the class object for a Java type.
     * @return a string representing the name of the get method, null in error case
     */
    public static String getSQLGetMethod(Class c) {
        return (JavaTypesForSQL.getSQLGetMethod(c));
    }

    /**
     * Returns the name of the setXXX method associated with the given type in
     * java.sql.ResultSet.
     * @param  c the class object for a Java type.
     * @return a string representing the name of the set method, null in error case
     */
    public static String getSQLSetMethod(Class c) {
        return (JavaTypesForSQL.getSQLSetMethod(c));
    }

    /**
     * Returns true if the given method name is getObject() or setObject()
     */
    public static boolean isXxxObjectMethod(String name) {
        return (name.equals("getObject") || name.equals("setObject"));
    }

    /**
     * Returns the SQL Type mapping the given Java Type.
     * @param  c the class object for a Java type.
     * @return the SQL Type mapping the given Java Type, (Types.OTHER in error case)
     */
    public static String getSQLType(Class c) {
        return (JavaTypesForSQL.getSQLType(c));
    }

    /**
     * Returns the default value of the given Java Type.
     * @param  c the class object for a Java type.
     * @return the default value of the given Java Type
     */
    public static String getDefaultValue(Class c) {
        String val = null;
        if (c.isPrimitive()) {
            val = (String) tableDefaultValues.get(c);
        } else {
            val = new String("null");
        }
        if (val == null) {
            throw new Error("JavaType ERROR: No default value for the class " + c.getName());
        }
        return (val);
    }
    
    /**
     * Returns the wrapper type of the given Java Type.
     * @param  c the class object for a Java type.
     * @return the wrapper type of the given Java Type. Empty String for non primitive classes
     */
    public static String getWrapperType(Class c) {
        String val = null;
        if (c.isPrimitive()) {
            val = (String) tableWrapperType.get(c);
        } else {
            val = new String("");
        }
        if (val == null) {
            throw new Error("JavaType ERROR: No wrapper type for the class " + c.getName());
        }
        return (val);
    }

    public static Boolean toObject(boolean val) {
        return new Boolean(val);
    }
    public static Byte toObject(byte val) {
        return new Byte(val);
    }
    public static Short toObject(short val) {
        return new Short(val);
    }
    public static Integer toObject(int val) {
        return new Integer(val);
    }
    public static Long toObject(long val) {
        return new Long(val);
    }
    public static Float toObject(float val) {
        return new Float(val);
    }
    public static Double toObject(double val) {
        return new Double(val);
    }
    public static Character toObject(char val) {
        return new Character(val);
    }
    public static Object toObject(Object val) {
        return val;
    }

    /**
     * If it is a primitive type, return a new Object constructor,
     * else return the same object.
     * There are nine predefined Class objects to represent the eight
     * primitive types and void. These are created by the Java Virtual Machine,
     * and have the same names as the primitive types that they represent,
     * namely boolean, byte, char, short, int, long, float, and double.
     * @param name name of the var
     * @param val the object value
     * @return new object 
     */
    public static String toStringObject(String name, Class c) {
        if (c.isPrimitive()) {
            if (c == Boolean.TYPE) {
                return "Boolean.valueOf(" + name + ")";
            } else if (c == Byte.TYPE) {
                return "new Byte(" + name + ")";
            } else if (c == Character.TYPE) {
                return "new Character(" + name + ")";
            } else if (c == Short.TYPE) {
                return "new Short(" + name + ")";
            } else if (c == Integer.TYPE) {
                return "new Integer(" + name + ")";
            } else if (c == Long.TYPE) {
                return "new Long(" + name + ")";
            } else if (c == Float.TYPE) {
                return "new Float(" + name + ")";
            } else if (c == Double.TYPE) {
                return "new Double(" + name + ")";
            } else {
                return name;
            }
        } else {
            return name;
        }
    }
    

}


/**
 * This inner class allows to calculate, for a given Java Type:
 * - the corresponding SQL type,
 * - the corresponding set method of the java.sql.PreparedStatement,
 * - the corresponding get method of the java.sql.ResultSet.
 */
class JavaTypesForSQL {

    private static JavaTypesForSQL mTypesForSQL = new JavaTypesForSQL();
    private static Hashtable mGets;
    private static Hashtable mSets;
    private static Hashtable mSQLTypes;

    private JavaTypesForSQL() {

        mGets = new Hashtable();
        mSets = new Hashtable();
        mSQLTypes = new Hashtable();

        mGets.put(java.lang.Boolean.TYPE, "getBoolean");
        mSets.put(java.lang.Boolean.TYPE, "setBoolean");
        mSQLTypes.put(java.lang.Boolean.TYPE, "Types.BIT");
        mGets.put(java.lang.Byte.TYPE, "getByte");
        mSets.put(java.lang.Byte.TYPE, "setByte");
        mSQLTypes.put(java.lang.Byte.TYPE, "Types.TINYINT");
        mGets.put(java.lang.Short.TYPE, "getShort");
        mSets.put(java.lang.Short.TYPE, "setShort");
        mSQLTypes.put(java.lang.Short.TYPE, "Types.SMALLINT");
        mGets.put(java.lang.Integer.TYPE, "getInt");
        mSets.put(java.lang.Integer.TYPE, "setInt");
        mSQLTypes.put(java.lang.Integer.TYPE, "Types.INTEGER");
        mGets.put(java.lang.Long.TYPE, "getLong");
        mSets.put(java.lang.Long.TYPE, "setLong");
        mSQLTypes.put(java.lang.Long.TYPE, "Types.BIGINT");
        mGets.put(java.lang.Float.TYPE, "getFloat");
        mSets.put(java.lang.Float.TYPE, "setFloat");
        mSQLTypes.put(java.lang.Float.TYPE, "Types.FLOAT");
        mGets.put(java.lang.Double.TYPE, "getDouble");
        mSets.put(java.lang.Double.TYPE, "setDouble");
        mSQLTypes.put(java.lang.Double.TYPE, "Types.DOUBLE");
        //mGets.put(java.lang.Character.TYPE, "????");
        //mSets.put(java.lang.Character.TYPE, "????");
        //mSQLTypes.put(java.lang.Character.TYPE, "???");
        mGets.put(java.lang.String.class, "getString");
        mSets.put(java.lang.String.class, "setString");
        mSQLTypes.put(java.lang.String.class, "Types.VARCHAR");
        mGets.put(java.sql.Date.class, "getDate");
        mSets.put(java.sql.Date.class, "setDate");
        mSQLTypes.put(java.sql.Date.class, "Types.DATE");
        mGets.put(java.sql.Time.class, "getTime");
        mSets.put(java.sql.Time.class, "setTime");
        mSQLTypes.put(java.sql.Time.class, "Types.TIME");
        mGets.put(java.sql.Timestamp.class, "getTimestamp");
        mSets.put(java.sql.Timestamp.class, "setTimestamp");
        mSQLTypes.put(java.sql.Timestamp.class, "Types.TIMESTAMP");
        mGets.put(java.lang.Boolean.class, "getBoolean");
        mSets.put(java.lang.Boolean.class, "setObject");
        mSQLTypes.put(java.lang.Boolean.class, "Types.BIT");
        mGets.put(java.lang.Integer.class, "getInt");
        mSets.put(java.lang.Integer.class, "setObject");
        mSQLTypes.put(java.lang.Integer.class, "Types.INTEGER");
        mGets.put(java.lang.Long.class, "getLong");
        mSets.put(java.lang.Long.class, "setObject");
        mSQLTypes.put(java.lang.Long.class, "Types.BIGINT");
        mGets.put(java.lang.Float.class, "getFloat");
        mSets.put(java.lang.Float.class, "setObject");
        mSQLTypes.put(java.lang.Float.class, "Types.REAL");
        mGets.put(java.lang.Double.class, "getDouble");
        mSets.put(java.lang.Double.class, "setObject");
        mSQLTypes.put(java.lang.Double.class, "Types.DOUBLE");
        mGets.put(java.lang.Byte.class, "getByte");
        mSets.put(java.lang.Byte.class, "setObject");
        mSQLTypes.put(java.lang.Byte.class, "Types.TINYINT");
        mGets.put(java.lang.Short.class, "getShort");
        mSets.put(java.lang.Short.class, "setObject");
        mSQLTypes.put(java.lang.Short.class, "Types.SMALLINT");
        mGets.put(java.math.BigDecimal.class, "getBigDecimal");
        mSets.put(java.math.BigDecimal.class, "setObject");
        mSQLTypes.put(java.math.BigDecimal.class, "Types.NUMERIC");
        mGets.put(java.math.BigInteger.class, "getBigDecimal");
        mSets.put(java.math.BigInteger.class, "setObject");
        mSQLTypes.put(java.math.BigInteger.class, "Types.NUMERIC");
    }

    /**
     * Returns the name of the getXXX method associated with the given type
     * in java.sql.ResultSet.
     * @param  c the class object for a Java type.
     * @return a string representing the name of the get method, null in error case
     */
    static String getSQLGetMethod(Class c) {
        String val = null;
        val = (String) mGets.get(c);
        if (val == null) {
            if (c.isArray()) {
                // See if c is byte[] or serializable[]
                Class  component = c.getComponentType();
                if (component != null) {
                    if (component.equals(java.lang.Byte.TYPE)) {
                        val = new String("getBytes");
                    } else if (JavaType.isSerializable(component)) {
                        val = new String("getSerializable");
                    }
                }
            } else if (JavaType.isSerializable(c)) {
                // See if c is serializable
                val = new String("getSerializable");
            }
        }
        return (val);
    }

    /**
     * Returns the name of the setXXX method associated with the given type
     * in java.sql.PreparedStatement.
     * @param  c the class object for a Java type.
     * @return a string representing the name of the set method, null in error case
     */
    static String getSQLSetMethod(Class c) {
        String val = null;
        val = (String) mSets.get(c);
        if (val == null) {
            if (c.isArray()) {
                // See if c is byte[] or serializable[]
                Class  component = c.getComponentType();
                if (component != null) {
                    if (component.equals(java.lang.Byte.TYPE)) {
                        val = new String("setBytes");
                    } else if (JavaType.isSerializable(component)) {
                        val = new String("setSerializable");
                    }
                }
            } else if (JavaType.isSerializable(c)) {
                // See if c is serializable
                val = new String("setSerializable");
            }
        }
        return (val);
    }

    /**
     * Returns the SQL Type mapping the given Java Type.
     * @param  c the class object for a Java type.
     * @return the SQL Type mapping the given Java Type, (Types.OTHER in error case)
     */
    static String getSQLType(Class c) {
        String val = null;
        val = (String) mSQLTypes.get(c);
        if (val == null) {
            val = new String("Types.OTHER");
            if (c.isArray()) {
                // See if c is byte[] or serializable[]
                Class  component = c.getComponentType();
                if (component != null) {
                    if (component.equals(java.lang.Byte.TYPE)) {
                        val = new String("Types.VARBINARY");
                    } else if (JavaType.isSerializable(component)) {
                        val = new String("Types.VARBINARY");
                    }
                }
            } else if (JavaType.isSerializable(c)) {
                // See if c is serializable
                val = new String("Types.VARBINARY");
            }
        }
        return (val);
    }

}



