org.pentaho.di.jdbc.Support.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.di.jdbc.Support.java

Source

/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/lgpl-2.0.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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 Lesser General Public License for more details.
*
* Copyright 2008 Bayon Technologies, Inc.  All rights reserved.
* Copyright (C) 2004 The jTDS Project
*/

package org.pentaho.di.jdbc;

import java.io.*;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.*;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.di.i18n.BaseMessages;

public class Support {
    private static Class<?> PKG = KettleDriver.class; // for i18n purposes, needed by Translator2!!   $NON-NLS-1$

    // Constants used in datatype conversions to avoid object allocations.
    private static final Integer INTEGER_ZERO = new Integer(0);
    private static final Integer INTEGER_ONE = new Integer(1);
    private static final Long LONG_ZERO = new Long(0L);
    private static final Long LONG_ONE = new Long(1L);
    private static final Float FLOAT_ZERO = new Float(0.0);
    private static final Float FLOAT_ONE = new Float(1.0);
    private static final Double DOUBLE_ZERO = new Double(0.0);
    private static final Double DOUBLE_ONE = new Double(1.0);
    private static final BigDecimal BIG_DECIMAL_ZERO = new BigDecimal(0.0);
    private static final BigDecimal BIG_DECIMAL_ONE = new BigDecimal(1.0);
    private static final java.sql.Date DATE_ZERO = new java.sql.Date(0);
    private static final java.sql.Time TIME_ZERO = new java.sql.Time(0);
    public static final BigInteger MAX_VALUE_28 = new BigInteger("9999999999999999999999999999");
    public static final BigInteger MAX_VALUE_38 = new BigInteger("99999999999999999999999999999999999999");

    private transient static final Log log = LogFactory.getLog(Support.class);

    /**
     * Convert java clases to java.sql.Type constant.
     */
    private static final HashMap<Class<?>, Integer> typeMap = new HashMap<Class<?>, Integer>();

    static {
        typeMap.put(Byte.class, new Integer(java.sql.Types.TINYINT));
        typeMap.put(Short.class, new Integer(java.sql.Types.SMALLINT));
        typeMap.put(Integer.class, new Integer(java.sql.Types.INTEGER));
        typeMap.put(Long.class, new Integer(java.sql.Types.BIGINT));
        typeMap.put(Float.class, new Integer(java.sql.Types.REAL));
        typeMap.put(Double.class, new Integer(java.sql.Types.DOUBLE));
        typeMap.put(BigDecimal.class, new Integer(java.sql.Types.DECIMAL));
        typeMap.put(Boolean.class, new Integer(java.sql.Types.BOOLEAN));
        typeMap.put(byte[].class, new Integer(java.sql.Types.VARBINARY));
        typeMap.put(java.sql.Date.class, new Integer(java.sql.Types.DATE));
        typeMap.put(java.sql.Time.class, new Integer(java.sql.Types.TIME));
        typeMap.put(java.sql.Timestamp.class, new Integer(java.sql.Types.TIMESTAMP));
        typeMap.put(BlobImpl.class, new Integer(java.sql.Types.LONGVARBINARY));
        typeMap.put(ClobImpl.class, new Integer(java.sql.Types.LONGVARCHAR));
        typeMap.put(String.class, new Integer(java.sql.Types.VARCHAR));
        typeMap.put(Blob.class, new Integer(java.sql.Types.LONGVARBINARY));
        typeMap.put(Clob.class, new Integer(java.sql.Types.LONGVARCHAR));
    }

    /**
     *  Hex constants to use in conversion routines.
     */
    private static final char hex[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
            'F' };

    /**
     * Static utility Calendar object.
     */
    private static final GregorianCalendar cal = new GregorianCalendar();

    /**
     * Convert a byte[] object to a hex string.
     *
     * @param bytes The byte array to convert.
     * @return The hex equivalent as a <code>String</code>.
     */
    public static String toHex(byte[] bytes) {
        int len = bytes.length;

        if (len > 0) {
            StringBuffer buf = new StringBuffer(len * 2);

            for (int i = 0; i < len; i++) {
                int b1 = bytes[i] & 0xFF;

                buf.append(hex[b1 >> 4]);
                buf.append(hex[b1 & 0x0F]);
            }

            return buf.toString();
        }

        return "";
    }

    /**
     * Normalize a BigDecimal value so that it fits within the
     * available precision.
     *
     * @param value The decimal value to normalize.
     * @param maxPrecision The decimal precision supported by the server
     *        (assumed to be a value of either 28 or 38).
     * @return The possibly normalized decimal value as a <code>BigDecimal</code>.
     * @throws SQLException If the number is too big.
     */
    static BigDecimal normalizeBigDecimal(BigDecimal value, int maxPrecision) throws SQLException {

        if (value == null) {
            return null;
        }

        if (value.scale() < 0) {
            // Java 1.5 BigDecimal allows negative scales.
            // jTDS cannot send these so re-scale.
            value = value.setScale(0);
        }

        if (value.scale() > maxPrecision) {
            // This is an optimization to quickly adjust the scale of a
            // very precise BD value. For example
            // BigDecimal((double)1.0/3.0) yields a BD 54 digits long!
            value = value.setScale(maxPrecision, BigDecimal.ROUND_HALF_UP);
        }

        BigInteger max = MAX_VALUE_38;

        while (value.abs().unscaledValue().compareTo(max) > 0) {
            // OK we need to reduce the scale if possible to preserve
            // the integer part of the number and still fit within the
            // available precision.
            int scale = value.scale() - 1;

            if (scale < 0) {
                // Can't do it number just too big
                throw new SQLException(
                        BaseMessages.getString(PKG, "error.normalize.numtoobig", String.valueOf(maxPrecision)),
                        "22000");
            }

            value = value.setScale(scale, BigDecimal.ROUND_HALF_UP);
        }

        return value;
    }

    /**
     * Convert an existing data object to the specified JDBC type.
     *
     * @param callerReference an object reference to the caller of this method;
     *                        must be a <code>Connection</code>,
     *                        <code>Statement</code> or <code>ResultSet</code>
     * @param x               the data object to convert
     * @param jdbcType        the required type constant from
     *                        <code>java.sql.Types</code>
     * @return the converted data object
     * @throws SQLException if the conversion is not supported or fails
     */
    static Object convert(Object callerReference, Object x, int jdbcType, String charSet) throws SQLException {
        try {
            switch (jdbcType) {
            case java.sql.Types.TINYINT:
            case java.sql.Types.SMALLINT:
            case java.sql.Types.INTEGER:
                if (x == null) {
                    return INTEGER_ZERO;
                } else if (x instanceof Integer) {
                    return x;
                } else if (x instanceof Byte) {
                    return new Integer(((Byte) x).byteValue() & 0xFF);
                } else if (x instanceof Number) {
                    return new Integer(((Number) x).intValue());
                } else if (x instanceof String) {
                    return new Integer(((String) x).trim());
                } else if (x instanceof Boolean) {
                    return ((Boolean) x).booleanValue() ? INTEGER_ONE : INTEGER_ZERO;
                }
                break;

            case java.sql.Types.BIGINT:
                if (x == null) {
                    return LONG_ZERO;
                } else if (x instanceof Long) {
                    return x;
                } else if (x instanceof Byte) {
                    return new Long(((Byte) x).byteValue() & 0xFF);
                } else if (x instanceof Number) {
                    return new Long(((Number) x).longValue());
                } else if (x instanceof String) {
                    return new Long(((String) x).trim());
                } else if (x instanceof Boolean) {
                    return ((Boolean) x).booleanValue() ? LONG_ONE : LONG_ZERO;
                }

                break;

            case java.sql.Types.REAL:
                if (x == null) {
                    return FLOAT_ZERO;
                } else if (x instanceof Float) {
                    return x;
                } else if (x instanceof Byte) {
                    return new Float(((Byte) x).byteValue() & 0xFF);
                } else if (x instanceof Number) {
                    return new Float(((Number) x).floatValue());
                } else if (x instanceof String) {
                    return new Float(((String) x).trim());
                } else if (x instanceof Boolean) {
                    return ((Boolean) x).booleanValue() ? FLOAT_ONE : FLOAT_ZERO;
                }

                break;

            case java.sql.Types.FLOAT:
            case java.sql.Types.DOUBLE:
                if (x == null) {
                    return DOUBLE_ZERO;
                } else if (x instanceof Double) {
                    return x;
                } else if (x instanceof Byte) {
                    return new Double(((Byte) x).byteValue() & 0xFF);
                } else if (x instanceof Number) {
                    return new Double(((Number) x).doubleValue());
                } else if (x instanceof String) {
                    return new Double(((String) x).trim());
                } else if (x instanceof Boolean) {
                    return ((Boolean) x).booleanValue() ? DOUBLE_ONE : DOUBLE_ZERO;
                }

                break;

            case java.sql.Types.NUMERIC:
            case java.sql.Types.DECIMAL:
                if (x == null) {
                    return null;
                } else if (x instanceof BigDecimal) {
                    return x;
                } else if (x instanceof Number) {
                    return new BigDecimal(x.toString());
                } else if (x instanceof String) {
                    return new BigDecimal((String) x);
                } else if (x instanceof Boolean) {
                    return ((Boolean) x).booleanValue() ? BIG_DECIMAL_ONE : BIG_DECIMAL_ZERO;
                }

                break;

            case java.sql.Types.VARCHAR:
            case java.sql.Types.CHAR:
                if (x == null) {
                    return null;
                } else if (x instanceof String) {
                    return x;
                } else if (x instanceof Number) {
                    return x.toString();
                } else if (x instanceof Boolean) {
                    return ((Boolean) x).booleanValue() ? "1" : "0";
                } else if (x instanceof Clob) {
                    Clob clob = (Clob) x;
                    long length = clob.length();

                    if (length > Integer.MAX_VALUE) {
                        throw new SQLException(BaseMessages.getString(PKG, "error.normalize.lobtoobig"), "22000");
                    }

                    return clob.getSubString(1, (int) length);
                } else if (x instanceof Blob) {
                    Blob blob = (Blob) x;
                    long length = blob.length();

                    if (length > Integer.MAX_VALUE) {
                        throw new SQLException(BaseMessages.getString(PKG, "error.normalize.lobtoobig"), "22000");
                    }

                    x = blob.getBytes(1, (int) length);
                }

                if (x instanceof byte[]) {
                    return toHex((byte[]) x);
                }

                return x.toString(); // Last hope!

            case java.sql.Types.BIT:
            case java.sql.Types.BOOLEAN:
                if (x == null) {
                    return Boolean.FALSE;
                } else if (x instanceof Boolean) {
                    return x;
                } else if (x instanceof Number) {
                    return (((Number) x).intValue() == 0) ? Boolean.FALSE : Boolean.TRUE;
                } else if (x instanceof String) {
                    String tmp = ((String) x).trim();

                    return ("1".equals(tmp) || "true".equalsIgnoreCase(tmp)) ? Boolean.TRUE : Boolean.FALSE;
                }

                break;

            case java.sql.Types.VARBINARY:
            case java.sql.Types.BINARY:
                if (x == null) {
                    return null;
                } else if (x instanceof byte[]) {
                    return x;
                } else if (x instanceof Blob) {
                    Blob blob = (Blob) x;

                    return blob.getBytes(1, (int) blob.length());
                } else if (x instanceof Clob) {
                    Clob clob = (Clob) x;
                    long length = clob.length();

                    if (length > Integer.MAX_VALUE) {
                        throw new SQLException(BaseMessages.getString(PKG, "error.normalize.lobtoobig"), "22000");
                    }

                    x = clob.getSubString(1, (int) length);
                }

                if (x instanceof String) {
                    //
                    // Strictly speaking this conversion is not required by
                    // the JDBC standard but jTDS has always supported it.
                    //
                    if (charSet == null) {
                        charSet = "ISO-8859-1";
                    }

                    try {
                        return ((String) x).getBytes(charSet);
                    } catch (UnsupportedEncodingException e) {
                        return ((String) x).getBytes();
                    }
                } else if (x instanceof UniqueIdentifier) {
                    return ((UniqueIdentifier) x).getBytes();
                }

                break;

            case java.sql.Types.TIMESTAMP:
                if (x == null) {
                    return null;
                } else if (x instanceof DateTime) {
                    return ((DateTime) x).toTimestamp();
                } else if (x instanceof java.sql.Timestamp) {
                    return x;
                } else if (x instanceof java.sql.Date) {
                    return new java.sql.Timestamp(((java.sql.Date) x).getTime());
                } else if (x instanceof java.sql.Time) {
                    return new java.sql.Timestamp(((java.sql.Time) x).getTime());
                } else if (x instanceof java.lang.String) {
                    return java.sql.Timestamp.valueOf(((String) x).trim());
                }

                break;

            case java.sql.Types.DATE:
                if (x == null) {
                    return null;
                } else if (x instanceof DateTime) {
                    return ((DateTime) x).toDate();
                } else if (x instanceof java.sql.Date) {
                    return x;
                } else if (x instanceof java.sql.Time) {
                    return DATE_ZERO;
                } else if (x instanceof java.sql.Timestamp) {
                    synchronized (cal) {
                        cal.setTime((java.util.Date) x);
                        cal.set(Calendar.HOUR_OF_DAY, 0);
                        cal.set(Calendar.MINUTE, 0);
                        cal.set(Calendar.SECOND, 0);
                        cal.set(Calendar.MILLISECOND, 0);
                        // VM1.4+ only              return new java.sql.Date(cal.getTimeInMillis());
                        return new java.sql.Date(cal.getTime().getTime());
                    }
                } else if (x instanceof java.lang.String) {
                    return java.sql.Date.valueOf(((String) x).trim());
                }

                break;

            case java.sql.Types.TIME:
                if (x == null) {
                    return null;
                } else if (x instanceof DateTime) {
                    return ((DateTime) x).toTime();
                } else if (x instanceof java.sql.Time) {
                    return x;
                } else if (x instanceof java.sql.Date) {
                    return TIME_ZERO;
                } else if (x instanceof java.sql.Timestamp) {
                    synchronized (cal) {
                        // VM 1.4+ only             cal.setTimeInMillis(((java.sql.Timestamp)x).getTime());
                        cal.setTime((java.util.Date) x);
                        cal.set(Calendar.YEAR, 1970);
                        cal.set(Calendar.MONTH, 0);
                        cal.set(Calendar.DAY_OF_MONTH, 1);
                        // VM 1.4+ only             return new java.sql.Time(cal.getTimeInMillis());*/
                        return new java.sql.Time(cal.getTime().getTime());
                    }
                } else if (x instanceof java.lang.String) {
                    return java.sql.Time.valueOf(((String) x).trim());
                }

                break;

            case java.sql.Types.OTHER:
                return x;

            case java.sql.Types.JAVA_OBJECT:
                throw new SQLException(BaseMessages.getString(PKG, "error.convert.badtypes", x.getClass().getName(),
                        getJdbcTypeName(jdbcType)), "22005");

            case java.sql.Types.LONGVARBINARY:
            case java.sql.Types.BLOB:
                if (x == null) {
                    return null;
                } else if (x instanceof Blob) {
                    return x;
                } else if (x instanceof byte[]) {
                    return new BlobImpl(getConnection(callerReference), (byte[]) x);
                } else if (x instanceof Clob) {
                    //
                    // Convert CLOB to BLOB. Not required by the standard but we will
                    // do it anyway.
                    //
                    Clob clob = (Clob) x;
                    try {
                        if (charSet == null) {
                            charSet = "ISO-8859-1";
                        }
                        Reader rdr = clob.getCharacterStream();
                        BlobImpl blob = new BlobImpl(getConnection(callerReference));
                        BufferedWriter out = new BufferedWriter(
                                new OutputStreamWriter(blob.setBinaryStream(1), charSet));
                        // TODO Use a buffer to improve performance
                        int c;
                        while ((c = rdr.read()) >= 0) {
                            out.write(c);
                        }
                        out.close();
                        rdr.close();
                        return blob;
                    } catch (UnsupportedEncodingException e) {
                        // Unlikely to happen but fall back on in memory copy
                        x = clob.getSubString(1, (int) clob.length());
                    } catch (IOException e) {
                        throw new SQLException(BaseMessages.getString(PKG, "error.generic.ioerror", e.getMessage()),
                                "HY000");
                    }
                }

                if (x instanceof String) {
                    //
                    // Strictly speaking this conversion is also not required by
                    // the JDBC standard but jTDS has always supported it.
                    //
                    BlobImpl blob = new BlobImpl(getConnection(callerReference));
                    String data = (String) x;

                    if (charSet == null) {
                        charSet = "ISO-8859-1";
                    }

                    try {
                        blob.setBytes(1, data.getBytes(charSet));
                    } catch (UnsupportedEncodingException e) {
                        blob.setBytes(1, data.getBytes());
                    }

                    return blob;
                }

                break;

            case java.sql.Types.LONGVARCHAR:
            case java.sql.Types.CLOB:
                if (x == null) {
                    return null;
                } else if (x instanceof Clob) {
                    return x;
                } else if (x instanceof Blob) {
                    //
                    // Convert BLOB to CLOB
                    //
                    Blob blob = (Blob) x;
                    try {
                        InputStream is = blob.getBinaryStream();
                        ClobImpl clob = new ClobImpl(getConnection(callerReference));
                        Writer out = clob.setCharacterStream(1);
                        // TODO Use a buffer to improve performance
                        int b;
                        // These reads/writes are buffered by the undelying blob buffers
                        while ((b = is.read()) >= 0) {
                            out.write(hex[b >> 4]);
                            out.write(hex[b & 0x0F]);
                        }
                        out.close();
                        is.close();
                        return clob;
                    } catch (IOException e) {
                        throw new SQLException(BaseMessages.getString(PKG, "error.generic.ioerror", e.getMessage()),
                                "HY000");
                    }
                } else if (x instanceof Boolean) {
                    x = ((Boolean) x).booleanValue() ? "1" : "0";
                } else if (!(x instanceof byte[])) {
                    x = x.toString();
                }

                if (x instanceof byte[]) {
                    ClobImpl clob = new ClobImpl(getConnection(callerReference));
                    clob.setString(1, toHex((byte[]) x));

                    return clob;
                } else if (x instanceof String) {
                    return new ClobImpl(getConnection(callerReference), (String) x);
                }

                break;

            default:
                throw new SQLException(
                        BaseMessages.getString(PKG, "error.convert.badtypeconst", getJdbcTypeName(jdbcType)),
                        "HY004");
            }

            throw new SQLException(BaseMessages.getString(PKG, "error.convert.badtypes", x.getClass().getName(),
                    getJdbcTypeName(jdbcType)), "22005");
        } catch (NumberFormatException nfe) {
            throw new SQLException(
                    BaseMessages.getString(PKG, "error.convert.badnumber", getJdbcTypeName(jdbcType)), "22000");
        }
    }

    /**
     * Get the JDBC type constant which matches the supplied Object type.
     *
     * @param value The object to analyse.
     * @return The JDBC type constant as an <code>int</code>.
     */
    static int getJdbcType(Object value) {
        if (value == null) {
            return java.sql.Types.NULL;
        }

        return getJdbcType(value.getClass());
    }

    /**
     * Get the JDBC type constant which matches the supplied <code>Class</code>.
     *
     * @param typeClass the <code>Class</code> to analyse
     * @return          the JDBC type constant as an <code>int</code>
     */
    static int getJdbcType(Class<?> typeClass) {
        if (typeClass == null) {
            return java.sql.Types.JAVA_OBJECT;
        }

        Object type = typeMap.get(typeClass);

        if (type == null) {
            // not in typeMap - try recursion through superclass hierarchy
            return getJdbcType(typeClass.getSuperclass());
        }

        return ((Integer) type).intValue();
    }

    /**
     * Get a String describing the supplied JDBC type constant.
     *
     * @param jdbcType The constant to be decoded.
     * @return The text decode of the type constant as a <code>String</code>.
     */
    static String getJdbcTypeName(int jdbcType) {
        switch (jdbcType) {
        case java.sql.Types.ARRAY:
            return "ARRAY";
        case java.sql.Types.BIGINT:
            return "BIGINT";
        case java.sql.Types.BINARY:
            return "BINARY";
        case java.sql.Types.BIT:
            return "BIT";
        case java.sql.Types.BLOB:
            return "BLOB";
        case java.sql.Types.BOOLEAN:
            return "BOOLEAN";
        case java.sql.Types.CHAR:
            return "CHAR";
        case java.sql.Types.CLOB:
            return "CLOB";
        //            case JtdsStatement.DATALINK:       return "DATALINK";
        case java.sql.Types.DATE:
            return "DATE";
        case java.sql.Types.DECIMAL:
            return "DECIMAL";
        case java.sql.Types.DISTINCT:
            return "DISTINCT";
        case java.sql.Types.DOUBLE:
            return "DOUBLE";
        case java.sql.Types.FLOAT:
            return "FLOAT";
        case java.sql.Types.INTEGER:
            return "INTEGER";
        case java.sql.Types.JAVA_OBJECT:
            return "JAVA_OBJECT";
        case java.sql.Types.LONGVARBINARY:
            return "LONGVARBINARY";
        case java.sql.Types.LONGVARCHAR:
            return "LONGVARCHAR";
        case java.sql.Types.NULL:
            return "NULL";
        case java.sql.Types.NUMERIC:
            return "NUMERIC";
        case java.sql.Types.OTHER:
            return "OTHER";
        case java.sql.Types.REAL:
            return "REAL";
        case java.sql.Types.REF:
            return "REF";
        case java.sql.Types.SMALLINT:
            return "SMALLINT";
        case java.sql.Types.STRUCT:
            return "STRUCT";
        case java.sql.Types.TIME:
            return "TIME";
        case java.sql.Types.TIMESTAMP:
            return "TIMESTAMP";
        case java.sql.Types.TINYINT:
            return "TINYINT";
        case java.sql.Types.VARBINARY:
            return "VARBINARY";
        case java.sql.Types.VARCHAR:
            return "VARCHAR";
        default:
            return "ERROR";
        }
    }

    /**
     * Retrieve the fully qualified java class name for the
     * supplied JDBC Types constant.
     *
     * @param jdbcType The JDBC Types constant.
     * @return The fully qualified java class name as a <code>String</code>.
     */
    static String getClassName(int jdbcType) {
        switch (jdbcType) {
        case java.sql.Types.BOOLEAN:
        case java.sql.Types.BIT:
            return "java.lang.Boolean";

        case java.sql.Types.TINYINT:
        case java.sql.Types.SMALLINT:
        case java.sql.Types.INTEGER:
            return "java.lang.Integer";

        case java.sql.Types.BIGINT:
            return "java.lang.Long";

        case java.sql.Types.NUMERIC:
        case java.sql.Types.DECIMAL:
            return "java.math.BigDecimal";

        case java.sql.Types.REAL:
            return "java.lang.Float";

        case java.sql.Types.FLOAT:
        case java.sql.Types.DOUBLE:
            return "java.lang.Double";

        case java.sql.Types.CHAR:
        case java.sql.Types.VARCHAR:
            return "java.lang.String";

        case java.sql.Types.BINARY:
        case java.sql.Types.VARBINARY:
            return "[B";

        case java.sql.Types.LONGVARBINARY:
        case java.sql.Types.BLOB:
            return "java.sql.Blob";

        case java.sql.Types.LONGVARCHAR:
        case java.sql.Types.CLOB:
            return "java.sql.Clob";

        case java.sql.Types.DATE:
            return "java.sql.Date";

        case java.sql.Types.TIME:
            return "java.sql.Time";

        case java.sql.Types.TIMESTAMP:
            return "java.sql.Timestamp";
        default:
            break;
        }

        return "java.lang.Object";
    }

    /**
     * Embed the data object as a string literal in the buffer supplied.
     *
     * @param buf The buffer in which the data will be embeded.
     * @param value The data object.
     * @param isUnicode Set to <code>true</code> if Unicode strings should be used, else <code>false</code>.
     * @param connection The {@link ConnectionJDBC3} object.
     */
    static void embedData(StringBuffer buf, Object value, boolean isUnicode, ConnectionJDBC3 connection)
            throws SQLException {
        buf.append(' ');
        if (value == null) {
            buf.append("NULL ");
            return;
        }

        if (value instanceof Blob) {
            Blob blob = (Blob) value;

            value = blob.getBytes(1, (int) blob.length());
        } else if (value instanceof Clob) {
            Clob clob = (Clob) value;

            value = clob.getSubString(1, (int) clob.length());
        }

        if (value instanceof DateTime) {
            buf.append('\'');
            buf.append(value);
            buf.append('\'');
        } else if (value instanceof byte[]) {
            byte[] bytes = (byte[]) value;

            int len = bytes.length;

            if (len >= 0) {
                buf.append('0').append('x');
                if (len == 0) {
                    // Zero length binary values are not allowed
                    buf.append('0').append('0');
                } else {
                    for (int i = 0; i < len; i++) {
                        int b1 = bytes[i] & 0xFF;

                        buf.append(hex[b1 >> 4]);
                        buf.append(hex[b1 & 0x0F]);
                    }
                }
            }
        } else if (value instanceof String) {
            String tmp = (String) value;
            int len = tmp.length();

            if (isUnicode) {
                buf.append('N');
            }
            buf.append('\'');

            for (int i = 0; i < len; i++) {
                char c = tmp.charAt(i);

                if (c == '\'') {
                    buf.append('\'');
                }

                buf.append(c);
            }

            buf.append('\'');
        } else if (value instanceof java.sql.Date) {
            DateTime dt = new DateTime((java.sql.Date) value);
            buf.append('\'');
            buf.append(dt);
            buf.append('\'');
        } else if (value instanceof java.sql.Time) {
            DateTime dt = new DateTime((java.sql.Time) value);
            buf.append('\'');
            buf.append(dt);
            buf.append('\'');
        } else if (value instanceof java.sql.Timestamp) {
            DateTime dt = new DateTime((java.sql.Timestamp) value);
            buf.append('\'');
            buf.append(dt);
            buf.append('\'');
        } else if (value instanceof Boolean) {
            buf.append(((Boolean) value).booleanValue() ? '1' : '0');
        } else if (value instanceof BigDecimal) {
            //
            // Ensure large decimal number does not overflow the
            // maximum precision of the server.
            // Main problem is with small numbers e.g. BigDecimal(1.0).toString() =
            // 0.1000000000000000055511151231....
            //
            String tmp = value.toString();
            int maxlen = connection.getMaxPrecision();
            if (tmp.charAt(0) == '-') {
                maxlen++;
            }
            if (tmp.indexOf('.') >= 0) {
                maxlen++;
            }
            if (tmp.length() > maxlen) {
                buf.append(tmp.substring(0, maxlen));
            } else {
                buf.append(tmp);
            }
        } else {
            buf.append(value.toString());
        }
        buf.append(' ');
    }

    /**
     * Encode a string into a byte array using the specified character set.
     *
     * @param cs The Charset name.
     * @param value The value to encode.
     * @return The value of the String as a <code>byte[]</code>.
     */
    static byte[] encodeString(String cs, String value) {
        try {
            return value.getBytes(cs);
        } catch (UnsupportedEncodingException e) {
            return value.getBytes();
        }
    }

    /**
     * Link the original cause to an <code>SQLWarning</code>.
     * <p>
     * This convenience method calls {@link #linkException(Exception, Throwable)}
     * and casts the result for cleaner code elsewhere.
     *
     * @param sqle The <code>SQLWarning</code> to enhance.
     * @param cause The <code>Throwable</code> to link.
     * @return The original <code>SQLWarning</code> object.
     */
    public static SQLWarning linkException(SQLWarning sqle, Throwable cause) {
        return (SQLWarning) linkException((Exception) sqle, cause);
    }

    /**
     * Link the original cause to an <code>SQLException</code>.
     * <p>
     * This convenience method calls {@link #linkException(Exception, Throwable)}
     * and casts the result for cleaner code elsewhere.
     *
     * @param sqle The <code>SQLException</code> to enhance.
     * @param cause The <code>Throwable</code> to link.
     * @return The original <code>SQLException</code> object.
     */
    public static SQLException linkException(SQLException sqle, Throwable cause) {
        return (SQLException) linkException((Exception) sqle, cause);
    }

    /**
     * Link the original cause to an <code>Exception</code>.
     * <p>
     * If running under JVM 1.4+ the <code>Throwable.initCause(Throwable)</code>
     * method will be invoked to chain the exception, else the exception is
     * logged via the {@link Logger} class.
     * Modeled after the code written by Brian Heineman.
     *
     * @param exception The <code>Exception</code> to enhance.
     * @param cause The <code>Throwable</code> to link.
     * @return The original <code>Exception</code> object.
     */
    public static Throwable linkException(Exception exception, Throwable cause) {
        Class<?> exceptionClass = exception.getClass();
        Class<?>[] parameterTypes = new Class<?>[] { Throwable.class };
        Object[] arguments = new Object[] { cause };

        try {
            Method initCauseMethod = exceptionClass.getMethod("initCause", parameterTypes);
            initCauseMethod.invoke(exception, arguments);
        } catch (NoSuchMethodException e) {
            log.error(e);
        } catch (Exception e) {
            // Ignore all other exceptions.  Do not prevent the main exception
            // from being returned if reflection fails for any reason.
        }

        return exception;
    }

    /**
     * Convert a timestamp to a different Timezone.
     *
     * @param value  the timestamp value
     * @param target the <code>Calendar</code> containing the TimeZone
     * @return the new timestamp value as a <code>long</code>
     */
    public static long timeToZone(java.util.Date value, Calendar target) {
        synchronized (cal) {
            java.util.Date tmp = target.getTime();
            try {
                cal.setTime(value);

                target.set(Calendar.HOUR_OF_DAY, cal.get(Calendar.HOUR_OF_DAY));
                target.set(Calendar.MINUTE, cal.get(Calendar.MINUTE));
                target.set(Calendar.SECOND, cal.get(Calendar.SECOND));
                target.set(Calendar.MILLISECOND, cal.get(Calendar.MILLISECOND));
                target.set(Calendar.YEAR, cal.get(Calendar.YEAR));
                target.set(Calendar.MONTH, cal.get(Calendar.MONTH));
                target.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH));
                return target.getTime().getTime();
            } finally {
                target.setTime(tmp);
            }
        }
    }

    /**
     * Convert a timestamp from a different Timezone.
     * @param value the timestamp value.
     * @param target the Calendar containing the TimeZone.
     * @return The new timestamp value as a <code>long</code>.
     */
    public static long timeFromZone(java.util.Date value, Calendar target) {
        synchronized (cal) {
            java.util.Date tmp = target.getTime();
            try {
                target.setTime(value);

                cal.set(Calendar.HOUR_OF_DAY, target.get(Calendar.HOUR_OF_DAY));
                cal.set(Calendar.MINUTE, target.get(Calendar.MINUTE));
                cal.set(Calendar.SECOND, target.get(Calendar.SECOND));
                cal.set(Calendar.MILLISECOND, target.get(Calendar.MILLISECOND));
                cal.set(Calendar.YEAR, target.get(Calendar.YEAR));
                cal.set(Calendar.MONTH, target.get(Calendar.MONTH));
                cal.set(Calendar.DAY_OF_MONTH, target.get(Calendar.DAY_OF_MONTH));
                return cal.getTime().getTime();
            } finally {
                target.setTime(tmp);
            }
        }
    }

    /**
     * Converts a LOB to the equivalent Java type, i.e. <code>Clob</code> to
     * <code>String</code> and <code>Blob</code> to <code>byte[]</code>. If the
     * value passed is not a LOB object, it is left unchanged and no exception
     * is thrown; the idea is to transparently convert only LOBs.
     *
     * @param value an object that may be a LOB
     * @return if the value was a LOB, the equivalent Java object, otherwise
     *         the original value
     * @throws SQLException if an error occurs while reading the LOB contents
     */
    public static Object convertLOB(Object value) throws SQLException {
        if (value instanceof Clob) {
            Clob c = (Clob) value;
            return c.getSubString(1, (int) c.length());
        }

        if (value instanceof Blob) {
            Blob b = (Blob) value;
            return b.getBytes(1, (int) b.length());
        }

        return value;
    }

    /**
     * Converts a LOB type constant to the equivalent Java type constant, i.e.
     * <code>Types.CLOB</code> to <code>Types.LONGVARCHAR</code> and
     * <code>Types.BLOB</code> to <code>Types.LONGVARBINARY</code>. If the
     * type passed is not that of a LOB, it is left unchanged and no exception
     * is thrown; the idea is to transparently convert only LOB types.
     *
     * @param type a {@link Types} constant defining a JDBC type, possibly a
     *             LOB
     * @return if the type was that of a LOB, the equivalent Java object type,
     *         otherwise the original type
     */
    public static int convertLOBType(int type) {
        switch (type) {
        case Types.BLOB:
            return Types.LONGVARBINARY;
        case Types.CLOB:
            return Types.LONGVARCHAR;
        default:
            return type;
        }
    }

    /**
     * Checks the <code>os.name</code> system property to see if it starts
     * with "windows".
     *
     * @return <code>true</code> if <code>os.name</code> starts with "windows",
     * else <code>false</code>.
     */
    public static boolean isWindowsOS() {
        return System.getProperty("os.name").toLowerCase().startsWith("windows");
    }

    // ------------- Private methods  ---------

    /**
     * Returns the connection for a given <code>ResultSet</code>,
     * <code>Statement</code> or <code>Connection</code> object.
     *
     * @param callerReference an object reference to the caller of this method;
     *        must be a <code>Connection</code>, <code>Statement</code> or
     *        <code>ResultSet</code>
     * @return a connection
     */
    private static ConnectionJDBC3 getConnection(Object callerReference) {
        if (callerReference == null) {
            throw new IllegalArgumentException("callerReference cannot be null.");
        }

        Connection connection;

        try {
            if (callerReference instanceof Connection) {
                connection = (Connection) callerReference;
            } else if (callerReference instanceof Statement) {
                connection = ((Statement) callerReference).getConnection();
            } else if (callerReference instanceof ResultSet) {
                connection = ((ResultSet) callerReference).getStatement().getConnection();
            } else {
                throw new IllegalArgumentException("callerReference is invalid.");
            }
        } catch (SQLException e) {
            throw new IllegalStateException(e.getMessage());
        }

        return (ConnectionJDBC3) connection;
    }

    private Support() {
        // Prevent an instance of this class being created.
    }

}