Package com.mysql.clusterj.tie

Source Code of com.mysql.clusterj.tie.Utility$SingleByteCharsetConverter

/*
*  Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; version 2 of the License.
*
*  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 for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
*/

package com.mysql.clusterj.tie;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import com.mysql.ndbjtie.mysql.CharsetMap;
import com.mysql.ndbjtie.mysql.CharsetMapConst;
import com.mysql.ndbjtie.mysql.Utils;
import com.mysql.ndbjtie.ndbapi.NdbErrorConst;
import com.mysql.ndbjtie.ndbapi.NdbRecAttr;
import com.mysql.clusterj.ClusterJDatastoreException;
import com.mysql.clusterj.ClusterJFatalInternalException;
import com.mysql.clusterj.ClusterJUserException;
import com.mysql.clusterj.core.store.Column;
import com.mysql.clusterj.core.util.I18NHelper;
import com.mysql.clusterj.core.util.Logger;
import com.mysql.clusterj.core.util.LoggerFactoryService;
import com.mysql.clusterj.tie.DbImpl.BufferManager;

/** This class provides utility methods.
*
*/
public class Utility {

    /** My message translator */
    static final I18NHelper local = I18NHelper
            .getInstance(Utility.class);

    /** My logger */
    static final Logger logger = LoggerFactoryService.getFactory()
            .getInstance(Utility.class);

    /** Standard Java charset encoder */
    static CharsetEncoder charsetEncoder = Charset.forName("windows-1252").newEncoder();

    /** Standard Java charset decoder */
    static CharsetDecoder charsetDecoder = Charset.forName("windows-1252").newDecoder();

    static final long ooooooooooooooff = 0x00000000000000ffL;
    static final long ooooooooooooffoo = 0x000000000000ff00L;
    static final long ooooooooooffoooo = 0x0000000000ff0000L;
    static final long ooooooooffoooooo = 0x00000000ff000000L;
    static final long ooooooffoooooooo = 0x000000ff00000000L;
    static final long ooooffoooooooooo = 0x0000ff0000000000L;
    static final long ooffoooooooooooo = 0x00ff000000000000L;
    static final long ffoooooooooooooo = 0xff00000000000000L;
    static final long ooooooooffffffff = 0x00000000ffffffffL;
    static final int ooooooff = 0x000000ff;
    static final int ooooffff = 0x0000ffff;
    static final int ooooffoo = 0x0000ff00;
    static final int ooffoooo = 0x00ff0000;

    static final char[] SPACE_PAD = new char[255];
    static {
        for (int i = 0; i < 255; ++i) {
            SPACE_PAD[i] = ' ';
        }
    }

    static final byte[] ZERO_PAD = new byte[255];
    static {
        for (int i = 0; i < 255; ++i) {
            ZERO_PAD[i] = (byte)0;
        }
    }

    static final byte[] EMPTY_BYTE_ARRAY = new byte[0];

    // TODO: this is intended to investigate a class loader issue with Sparc java
    // The idea is to force loading the CharsetMap native class prior to calling the static create method
    static Class<?> charsetMapClass = loadClass("com.mysql.ndbjtie.mysql.CharsetMap");
    static Class<?> loadClass(String className) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            throw new ClusterJUserException(local.message("ERR_Loading_Native_Class", className), e);
        }
    }

    // TODO: change this to a weak reference so we can call delete on it when not needed
    /** Note that mysql refers to charset number and charset name, but the number is
    * actually a collation number. The CharsetMap interface thus has methods like
    * getCharsetNumber(String charsetName) but what is returned is actually a collation number.
    */
    static CharsetMap charsetMap = createCharsetMap();

    // TODO: this is intended to investigate a class loader issue with Sparc java
    // The idea is to create the CharsetMap create method in a try/catch block to report the exact error
    static CharsetMap createCharsetMap() {
        StringBuilder builder = new StringBuilder();
        CharsetMap result = null;
        try {
            return CharsetMap.create();
        } catch (Throwable t1) {
            builder.append("CharsetMap.create() threw " + t1.getClass().getName() + ":" + t1.getMessage());
            try {
                Method charsetMapCreateMethod = charsetMapClass.getMethod("create", (Class[])null);
                result = (CharsetMap)charsetMapCreateMethod.invoke(null, (Object[])null);
                builder.append("charsetMapCreateMethod.invoke() succeeded:" + result);
            } catch (Throwable t2) {
                builder.append("charsetMapCreateMethod.invoke() threw " + t2.getClass().getName() + ":" + t2.getMessage());
            }
            throw new ClusterJUserException(builder.toString());
        }
    }

    /** The maximum mysql collation (charset) number. This is hard coded in <mysql>/include/my_sys.h */
    static int MAXIMUM_MYSQL_COLLATION_NUMBER = 256;

    /** The mysql collation number for the standard charset */
    static int collationLatin1 = charsetMap.getCharsetNumber("latin1");

    /** The mysql collation number for UTF16 */
    static protected final int collationUTF16 = charsetMap.getUTF16CharsetNumber();

    /** The mysql charset map */
    public static CharsetMap getCharsetMap() {
        return charsetMap;
    }

    /** The map of charset name to collations that share the charset name */
    private static Map<String, int[]> collationPeersMap = new TreeMap<String, int[]>();

    /** The ClusterJ charset converter for all multibyte charsets */
    private static CharsetConverter charsetConverterMultibyte = new MultiByteCharsetConverter();

    /** Charset converters */
    private static CharsetConverter[] charsetConverters =
        new CharsetConverter[MAXIMUM_MYSQL_COLLATION_NUMBER + 1];

    /** Initialize the the array of charset converters and the map of known collations that share the same charset */
    static {
        Map<String, List<Integer>> workingCollationPeersMap = new TreeMap<String, List<Integer>>();
        for (int collation = 1; collation <= MAXIMUM_MYSQL_COLLATION_NUMBER; ++collation) {
            String mysqlName = charsetMap.getMysqlName(collation);
            if (mysqlName != null) {
                if ((isMultibyteCollation(collation))) {
                    // multibyte collations all use the multibyte charset converter
                    charsetConverters[collation] = charsetConverterMultibyte;
                } else {
                    // find out if this charset name is already used by another (peer) collation
                    List<Integer> collations = workingCollationPeersMap.get(mysqlName);
                    if (collations == null) {
                        // this is the first collation to use this charset name
                        collations = new ArrayList<Integer>(8);
                        collations.add(collation);
                        workingCollationPeersMap.put(mysqlName, collations);
                    } else {
                        // add this collation to the list of (peer) collations
                        collations.add(collation);
                    }
                }
            }
        }
       
        for (Map.Entry<String, List<Integer>> workingCollationPeers: workingCollationPeersMap.entrySet()) {
            String mysqlName = workingCollationPeers.getKey();
            List<Integer> collations = workingCollationPeers.getValue();
            int[] collationArray = new int[collations.size()];
            int i = 0;
            for (Integer collation: collations) {
                collationArray[i++] = collation;
            }
            collationPeersMap.put(mysqlName, collationArray);
        }
        if (logger.isDetailEnabled()) {
            for (Map.Entry<String, int[]> collationEntry: collationPeersMap.entrySet()) {
                logger.detail("Utility collationMap " + collationEntry.getKey()
                        + " collations : " + Arrays.toString(collationEntry.getValue()));
            }
        }
        // initialize the charset converter for latin1 and its peer collations (after peers are known)
        addCollation(collationLatin1);
    }

    /** Determine if the exception is retriable
     * @param ex the exception
     * @return if the status is retriable
     */
    public static boolean isRetriable(ClusterJDatastoreException ex) {
        return NdbErrorConst.Status.TemporaryError == ex.getStatus();
    }

    private final static EndianManager endianManager = ByteOrder.BIG_ENDIAN.equals(ByteOrder.nativeOrder())?
        /*
         * Big Endian algorithms to convert NdbRecAttr buffer into primitive types
         */
        new EndianManager() {

        public boolean getBoolean(Column storeColumn, NdbRecAttr ndbRecAttr) {
            switch (storeColumn.getType()) {
                case Bit:
                    return ndbRecAttr.int32_value() == 1;
                case Tinyint:
                    return ndbRecAttr.int8_value() == 1;
                default:
                    throw new ClusterJUserException(
                            local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "boolean"));
            }
        }

        public byte getByte(Column storeColumn, NdbRecAttr ndbRecAttr) {
            switch (storeColumn.getType()) {
                case Bit:
                    return (byte)ndbRecAttr.int32_value();
                case Tinyint:
                case Year:
                    return ndbRecAttr.int8_value();
                default:
                    throw new ClusterJUserException(
                            local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "byte"));
            }
        }
        public short getShort(Column storeColumn, NdbRecAttr ndbRecAttr) {
            switch (storeColumn.getType()) {
                case Bit:
                    return (short)ndbRecAttr.int32_value();
                case Smallint:
                    return ndbRecAttr.short_value();
                default:
                    throw new ClusterJUserException(
                            local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
            }
        }
        public int getInt(Column storeColumn, NdbRecAttr ndbRecAttr) {
            switch (storeColumn.getType()) {
                case Bit:
                case Int:
                case Timestamp:
                    return ndbRecAttr.int32_value();
                case Date:
                    return ndbRecAttr.u_medium_value();
                case Time:
                    return ndbRecAttr.medium_value();
                default:
                    throw new ClusterJUserException(
                            local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "int"));
            }
        }
        public long getLong(Column storeColumn, NdbRecAttr ndbRecAttr) {
            switch (storeColumn.getType()) {
                case Bit:
                    long rawValue = ndbRecAttr.int64_value();
                    return (rawValue >>> 32) | (rawValue << 32);
                case Bigint:
                case Bigunsigned:
                    return ndbRecAttr.int64_value();
                case Datetime:
                    return unpackDatetime(ndbRecAttr.int64_value());
                case Timestamp:
                    return (((long)ndbRecAttr.int32_value()) & ooooooooffffffff) * 1000L;
                case Date:
                    return unpackDate(ndbRecAttr.u_medium_value());
                case Time:
                    return unpackTime(ndbRecAttr.medium_value());
                default:
                    throw new ClusterJUserException(
                            local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
            }
        }
        /** Put the low order three bytes of the input value into the ByteBuffer as a medium_value.
         * The format for medium value is always little-endian even on big-endian architectures.
         * Do not flip the buffer, as the caller will do that if needed.
         * @param byteBuffer the byte buffer
         * @param value the input value
         */
        public void put3byteInt(ByteBuffer byteBuffer, int value) {
            byteBuffer.put((byte)(value));           
            byteBuffer.put((byte)(value >> 8));
            byteBuffer.put((byte)(value >> 16));
        }

        public ByteBuffer convertValue(Column storeColumn, byte value) {
            ByteBuffer result;
            switch (storeColumn.getType()) {
                case Bit:
                    result = ByteBuffer.allocateDirect(4);
                    // bit fields are always stored in an int32
                    result.order(ByteOrder.BIG_ENDIAN);
                    result.putInt(value & 0xff);
                    result.flip();
                    return result;
                case Tinyint:
                case Year:
                    result = ByteBuffer.allocateDirect(1);
                    result.put(value);
                    result.flip();
                    return result;
                default:
                    throw new ClusterJUserException(local.message(
                            "ERR_Unsupported_Mapping", storeColumn.getType(), "byte"));
            }
        }

        public ByteBuffer convertValue(Column storeColumn, short value) {
            ByteBuffer result;
            switch (storeColumn.getType()) {
                case Bit:
                    result = ByteBuffer.allocateDirect(4);
                    // bit fields are always stored in an int32
                    result.order(ByteOrder.BIG_ENDIAN);
                    result.putInt(value & 0xffff);
                    result.flip();
                    return result;
                case Smallint:
                    result = ByteBuffer.allocateDirect(2);
                    result.order(ByteOrder.BIG_ENDIAN);
                    result.putShort(value);
                    result.flip();
                    return result;
                default:
                    throw new ClusterJUserException(local.message(
                            "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
            }
        }

        public ByteBuffer convertValue(Column storeColumn, int value) {
            ByteBuffer result = ByteBuffer.allocateDirect(4);
            switch (storeColumn.getType()) {
                case Bit:
                case Int:
                    result.order(ByteOrder.BIG_ENDIAN);
                    break;
                default:
                    throw new ClusterJUserException(local.message(
                            "ERR_Unsupported_Mapping", storeColumn.getType(), "int"));
            }
            result.putInt(value);
            result.flip();
            return result;
        }

        public ByteBuffer convertValue(Column storeColumn, long value) {
            ByteBuffer result = ByteBuffer.allocateDirect(8);
            return convertValue(storeColumn, value, result);
        }

        public ByteBuffer convertValue(Column storeColumn, long value, ByteBuffer result) {
            switch (storeColumn.getType()) {
                case Bit:
                    // bit fields are stored in two int32 fields
                    result.order(ByteOrder.BIG_ENDIAN);
                    result.putInt((int)((value)));
                    result.putInt((int)((value >>> 32)));
                    result.flip();
                    return result;
                case Bigint:
                case Bigunsigned:
                    result.order(ByteOrder.BIG_ENDIAN);
                    result.putLong(value);
                    result.flip();
                    return result;
                case Date:
                    result.order(ByteOrder.LITTLE_ENDIAN);
                    put3byteInt(result, packDate(value));
                    result.flip();
                    return result;
                case Datetime:
                    result.order(ByteOrder.BIG_ENDIAN);
                    result.putLong(packDatetime(value));
                    result.flip();
                    return result;
                case Time:
                    result.order(ByteOrder.LITTLE_ENDIAN);
                    put3byteInt(result, packTime(value));
                    result.flip();
                    return result;
                case Timestamp:
                    result.order(ByteOrder.BIG_ENDIAN);
                    result.putInt((int)(value/1000L));
                    result.flip();
                    return result;
                default:
                    throw new ClusterJUserException(local.message(
                            "ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
            }
        }

        public long convertLongValueForStorage(Column storeColumn, long value) {
            long result = 0L;
            switch (storeColumn.getType()) {
                case Bit:
                    // bit fields are stored in two int32 fields
                    result |= (value >>> 32);
                    result |= (value << 32);
                    return result;
                case Bigint:
                case Bigunsigned:
                    return value;
                case Date:
                    // the high order bytes are the little endian representation
                    // the original is 0000000000xxyyzz and the result is zzyyxx0000000000
                    long packDate = packDate(value);
                    result |= (packDate & ooooooff) << 56;
                    result |= (packDate & ooooffoo) << 40;
                    result |= (packDate & ooffoooo) << 24;
                    return result;
                case Datetime:
                    return packDatetime(value);
                case Time:
                    // the high order bytes are the little endian representation
                    // the original is 0000000000xxyyzz and the result is zzyyxx0000000000
                    long packTime = packTime(value);
                    result |= (packTime & ooooooff) << 56;
                    result |= (packTime & ooooffoo) << 40;
                    result |= (packTime & ooffoooo) << 24;
                    return result;
                case Timestamp:
                    // timestamp is an int so put the value into the high bytes
                    // the original is 00000000tttttttt and the result is tttttttt00000000
                    return (value/1000L) << 32;
                default:
                    throw new ClusterJUserException(local.message(
                            "ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
            }
        }

        public int convertByteValueForStorage(Column storeColumn, byte value) {
            switch (storeColumn.getType()) {
                case Bit:
                    // bit fields are always stored in an int32
                    return value & ooooooff;
                case Tinyint:
                case Year:
                    // other byte values are stored in the high byte of an int
                    return value << 24;
                default:
                    throw new ClusterJUserException(local.message(
                            "ERR_Unsupported_Mapping", storeColumn.getType(), "byte"));
            }
        }

        public int convertShortValueForStorage(Column storeColumn, short value) {
            switch (storeColumn.getType()) {
                case Bit:
                    // bit fields are always stored in an int32
                    return value & ooooffff;
                case Smallint:
                    // short values are in the top 16 bits of an int
                    return value << 16;
                default:
                    throw new ClusterJUserException(local.message(
                            "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
            }
        }

    }:
        /*
         * Little Endian algorithms to convert NdbRecAttr buffer into primitive types
         */
        new EndianManager() {

        public boolean getBoolean(Column storeColumn, NdbRecAttr ndbRecAttr) {
            switch (storeColumn.getType()) {
                case Bit:
                    return ndbRecAttr.int32_value() == 1;
                case Tinyint:
                    return ndbRecAttr.int8_value() == 1;
                default:
                    throw new ClusterJUserException(
                            local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "boolean"));
            }
        }

        public byte getByte(Column storeColumn, NdbRecAttr ndbRecAttr) {
            switch (storeColumn.getType()) {
                case Bit:
                case Tinyint:
                case Year:
                    return ndbRecAttr.int8_value();
                default:
                    throw new ClusterJUserException(
                            local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "byte"));
            }
        }
        public short getShort(Column storeColumn, NdbRecAttr ndbRecAttr) {
            switch (storeColumn.getType()) {
                case Bit:
                case Smallint:
                    return ndbRecAttr.short_value();
                default:
                    throw new ClusterJUserException(
                            local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
            }
        }
        public int getInt(Column storeColumn, NdbRecAttr ndbRecAttr) {
            switch (storeColumn.getType()) {
                case Bit:
                case Int:
                case Timestamp:
                    return ndbRecAttr.int32_value();
                case Date:
                    return ndbRecAttr.u_medium_value();
                case Time:
                    return ndbRecAttr.medium_value();
                default:
                    throw new ClusterJUserException(
                            local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "int"));
            }
        }
        public long getLong(Column storeColumn, NdbRecAttr ndbRecAttr) {
            switch (storeColumn.getType()) {
                case Bigint:
                case Bigunsigned:
                case Bit:
                    return ndbRecAttr.int64_value();
                case Datetime:
                    return unpackDatetime(ndbRecAttr.int64_value());
                case Timestamp:
                    return ndbRecAttr.int32_value() * 1000L;
                case Date:
                    return unpackDate(ndbRecAttr.int32_value());
                case Time:
                    return unpackTime(ndbRecAttr.int32_value());
                default:
                    throw new ClusterJUserException(
                            local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
            }
        }

        /** Put the low order three bytes of the input value into the ByteBuffer as a medium_value.
         * The format for medium value is always little-endian even on big-endian architectures.
         * Do not flip the buffer, as the caller will do that if needed.
         * @param byteBuffer the byte buffer
         * @param value the input value
         */
        public void put3byteInt(ByteBuffer byteBuffer, int value) {
            byteBuffer.putInt(value);
            byteBuffer.limit(3);
        }

        public ByteBuffer convertValue(Column storeColumn, byte value) {
            ByteBuffer result;
            switch (storeColumn.getType()) {
                case Bit:
                    // bit fields are always stored as int32
                    result = ByteBuffer.allocateDirect(4);
                    result.order(ByteOrder.nativeOrder());
                    result.putInt(value & 0xff);
                    result.flip();
                    return result;
                case Tinyint:
                case Year:
                    result = ByteBuffer.allocateDirect(1);
                    result.order(ByteOrder.nativeOrder());
                    result.put(value);
                    result.flip();
                    return result;
                default:
                    throw new ClusterJUserException(local.message(
                            "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
            }
        }

        public ByteBuffer convertValue(Column storeColumn, short value) {
            switch (storeColumn.getType()) {
                case Bit:
                case Smallint:
                    ByteBuffer result = ByteBuffer.allocateDirect(2);
                    result.order(ByteOrder.nativeOrder());
                    result.putShort(value);
                    result.flip();
                    return result;
                default:
                    throw new ClusterJUserException(local.message(
                            "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
            }
        }

        public ByteBuffer convertValue(Column storeColumn, int value) {
            switch (storeColumn.getType()) {
                case Bit:
                case Int:
                    ByteBuffer result = ByteBuffer.allocateDirect(4);
                    result.order(ByteOrder.nativeOrder());
                    result.putInt(value);
                    result.flip();
                    return result;
                default:
                    throw new ClusterJUserException(local.message(
                            "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
            }
        }

        public ByteBuffer convertValue(Column storeColumn, long value) {
            ByteBuffer result = ByteBuffer.allocateDirect(8);
            return convertValue(storeColumn, value, result);
        }

        public ByteBuffer convertValue(Column storeColumn, long value, ByteBuffer result) {
            switch (storeColumn.getType()) {
                case Bit:
                case Bigint:
                case Bigunsigned:
                    result.order(ByteOrder.LITTLE_ENDIAN);
                    result.putLong(value);
                    result.flip();
                    return result;
                case Datetime:
                    result.order(ByteOrder.LITTLE_ENDIAN);
                    result.putLong(packDatetime(value));
                    result.flip();
                    return result;
                case Timestamp:
                    result.order(ByteOrder.LITTLE_ENDIAN);
                    result.putInt((int)(value/1000L));
                    result.flip();
                    return result;
                case Date:
                    result.order(ByteOrder.LITTLE_ENDIAN);
                    put3byteInt(result, packDate(value));
                    result.flip();
                    return result;
                case Time:
                    result.order(ByteOrder.LITTLE_ENDIAN);
                    put3byteInt(result, packTime(value));
                    result.flip();
                    return result;
                default:
                    throw new ClusterJUserException(local.message(
                            "ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
            }
        }

        public long convertLongValueForStorage(Column storeColumn, long value) {
            switch (storeColumn.getType()) {
                case Bit:
                case Bigint:
                case Bigunsigned:
                    return value;
                case Datetime:
                    return packDatetime(value);
               case Timestamp:
                    return value/1000L;
                case Date:
                    return packDate(value);
                case Time:
                    return packTime(value);
                default:
                    throw new ClusterJUserException(local.message(
                            "ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
            }
        }

        public int convertByteValueForStorage(Column storeColumn, byte value) {
            switch (storeColumn.getType()) {
                case Bit:
                    // bit fields are always stored as int32
                case Tinyint:
                case Year:
                    return  value & ooooooff;
                default:
                    throw new ClusterJUserException(local.message(
                            "ERR_Unsupported_Mapping", storeColumn.getType(), "byte"));
            }
        }

        public int convertShortValueForStorage(Column storeColumn, short value) {
            switch (storeColumn.getType()) {
                case Bit:
                    // bit fields are always stored as int32
                case Smallint:
                    return value & ooooffff;
                default:
                    throw new ClusterJUserException(local.message(
                            "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
            }
        }

    };

    /* Error codes that are not severe, and simply reflect expected conditions */
    private static Set<Integer> NonSevereErrorCodes = new HashSet<Integer>();

    static {
        NonSevereErrorCodes.add(4203); // Trying to set a NOT NULL attribute to NULL
        NonSevereErrorCodes.add(4243); // Index not found
        NonSevereErrorCodes.add(626); // Tuple did not exist
    }

    protected static interface EndianManager {
        public void put3byteInt(ByteBuffer byteBuffer, int value);
        public int getInt(Column storeColumn, NdbRecAttr ndbRecAttr);
        public short getShort(Column storeColumn, NdbRecAttr ndbRecAttr);
        public long getLong(Column storeColumn, NdbRecAttr ndbRecAttr);
        public byte getByte(Column storeColumn, NdbRecAttr ndbRecAttr);
        public ByteBuffer convertValue(Column storeColumn, byte value);
        public ByteBuffer convertValue(Column storeColumn, short value);
        public ByteBuffer convertValue(Column storeColumn, int value);
        public ByteBuffer convertValue(Column storeColumn, long value);
        public boolean getBoolean(Column storeColumn, NdbRecAttr ndbRecAttr);
        public long convertLongValueForStorage(Column storeColumn, long value);
        public int convertByteValueForStorage(Column storeColumn, byte value);
        public int convertShortValueForStorage(Column storeColumn, short value);
    }

    /** Swap the bytes in the value, thereby converting a big-endian value
     * into a little-endian value (or vice versa).
     * @param value the value to be swapped
     * @return the swapped value
     */
    protected static short swap(short value) {
        return (short)((0x00ff & (value >>> 8)) |
                       (0xff00 & (value  << 8)));
    }

    /** Swap the bytes in the value, thereby converting a big-endian value
     * into a little-endian value (or vice versa).
     * @param value the value to be swapped
     * @return the swapped value
     */
    protected static int swap(int value) {
        return   0x000000ff & (value >>> 24) |
                (0x0000ff00 & (value >>> 8)) |
                (0x00ff0000 & (value  << 8)) |
                (0xff000000 & (value  << 24));
    }

    /** Swap the bytes in the value, thereby converting a big-endian value
     * into a little-endian value (or vice versa).
     * @param value the value to be swapped
     * @return the swapped value
     */
    protected static long swap(long value) {
        return   ooooooooooooooff & (value >>> 56) |
                (ooooooooooooffoo & (value >>> 40)) |
                (ooooooooooffoooo & (value >>> 24)) |
                (ooooooooffoooooo & (value >>> 8)) |
                (ooooooffoooooooo & (value  << 8)) |
                (ooooffoooooooooo & (value  << 24)) |
                (ooffoooooooooooo & (value  << 40)) |
                (ffoooooooooooooo & (value  << 56));
    }

    protected static void throwError(Object returnCode, NdbErrorConst ndbError) {
        throwError(returnCode, ndbError, "");
    }

    protected static void throwError(Object returnCode, NdbErrorConst ndbError, String extra) {
        String message = ndbError.message();
        int code = ndbError.code();
        int mysqlCode = ndbError.mysql_code();
        int status = ndbError.status();
        int classification = ndbError.classification();
        String msg = local.message("ERR_NdbJTie", returnCode, code, mysqlCode,
                status, classification, message, extra);
        if (!NonSevereErrorCodes .contains(code)) {
            logger.error(msg);
        }
        throw new ClusterJDatastoreException(msg, code, mysqlCode, status, classification);
    }

    /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
     *
     * @param storeColumn the column definition
     * @param value the value to be converted
     * @return the ByteBuffer
     */
    public static ByteBuffer convertValue(Column storeColumn, byte[] value) {
        int dataLength = value.length;
        int prefixLength = storeColumn.getPrefixLength();
        ByteBuffer result;
        switch (prefixLength) {
            case 0:
                int requiredLength = storeColumn.getColumnSpace();
                if (dataLength > requiredLength) {
                    throw new ClusterJFatalInternalException(
                            local.message("ERR_Data_Too_Long",
                            storeColumn.getName(), requiredLength, dataLength));
                } else {
                    result = ByteBuffer.allocateDirect(requiredLength);
                    result.order(ByteOrder.nativeOrder());
                    result.put(value);
                    if (dataLength < requiredLength) {
                        // pad with 0x00 on right
                        result.put(ZERO_PAD, 0, requiredLength - dataLength);
                    }
                }
                break;
            case 1:
                if (dataLength > 255) {
                    throw new ClusterJFatalInternalException(
                            local.message("ERR_Data_Too_Long",
                            storeColumn.getName(), "255", dataLength));
                }
                result = ByteBuffer.allocateDirect(prefixLength + dataLength);
                result.order(ByteOrder.nativeOrder());
                result.put((byte)dataLength);
                result.put(value);
                break;
            case 2:
                if (dataLength > 8000) {
                    throw new ClusterJFatalInternalException(
                            local.message("ERR_Data_Too_Long",
                            storeColumn.getName(), "8000", dataLength));
                }
                result = ByteBuffer.allocateDirect(prefixLength + dataLength);
                result.order(ByteOrder.nativeOrder());
                result.put((byte)(dataLength%256));
                result.put((byte)(dataLength/256));
                result.put(value);
                break;
            default:
                    throw new ClusterJFatalInternalException(
                            local.message("ERR_Unknown_Prefix_Length",
                            prefixLength, storeColumn.getName()));
        }
        result.flip();
        return result;
    }

    /** Convert a BigDecimal value to the binary decimal form used by MySQL.
     * Use the precision and scale of the column to convert. Values that don't fit
     * into the column throw a ClusterJUserException.
     * @param storeColumn the column metadata
     * @param value the value to be converted
     * @return the ByteBuffer
     */
    public static ByteBuffer convertValue(Column storeColumn, BigDecimal value) {
        int precision = storeColumn.getPrecision();
        int scale = storeColumn.getScale();
        int bytesNeeded = getDecimalColumnSpace(precision, scale);
        ByteBuffer result = ByteBuffer.allocateDirect(bytesNeeded);
        // TODO this should be a policy option, perhaps an annotation to fail on truncation
        BigDecimal scaledValue = value.setScale(scale, RoundingMode.HALF_UP);
        // the new value has the same scale as the column
        String stringRepresentation = scaledValue.toPlainString();
        int length = stringRepresentation.length();
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(length);
        CharBuffer charBuffer = CharBuffer.wrap(stringRepresentation);
        // basic encoding
        charsetEncoder.encode(charBuffer, byteBuffer, true);
        byteBuffer.flip();
        int returnCode = Utils.decimal_str2bin(
                byteBuffer, length, precision, scale, result, bytesNeeded);
        byteBuffer.flip();
        if (returnCode != 0) {
            throw new ClusterJUserException(
                    local.message("ERR_String_To_Binary_Decimal",
                    returnCode, scaledValue, storeColumn.getName(), precision, scale));
        }
        return result;
    }

    /** Convert a BigInteger value to the binary decimal form used by MySQL.
     * Use the precision and scale of the column to convert. Values that don't fit
     * into the column throw a ClusterJUserException.
     * @param storeColumn the column metadata
     * @param value the value to be converted
     * @return the ByteBuffer
     */
    public static ByteBuffer convertValue(Column storeColumn, BigInteger value) {
        int precision = storeColumn.getPrecision();
        int scale = storeColumn.getScale();
        int bytesNeeded = getDecimalColumnSpace(precision, scale);
        ByteBuffer result = ByteBuffer.allocateDirect(bytesNeeded);
        String stringRepresentation = value.toString();
        int length = stringRepresentation.length();
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(length);
        CharBuffer charBuffer = CharBuffer.wrap(stringRepresentation);
        // basic encoding
        charsetEncoder.encode(charBuffer, byteBuffer, true);
        byteBuffer.flip();
        int returnCode = Utils.decimal_str2bin(
                byteBuffer, length, precision, scale, result, bytesNeeded);
        byteBuffer.flip();
        if (returnCode != 0) {
            throw new ClusterJUserException(
                    local.message("ERR_String_To_Binary_Decimal",
                    returnCode, stringRepresentation, storeColumn.getName(), precision, scale));
        }
        return result;
    }

    /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
     *
     * @param storeColumn the column definition
     * @param value the value to be converted
     * @return the ByteBuffer
     */
    public static ByteBuffer convertValue(Column storeColumn, double value) {
        ByteBuffer result = ByteBuffer.allocateDirect(8);
        result.order(ByteOrder.nativeOrder());
        result.putDouble(value);
        result.flip();
        return result;
    }

    /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
     *
     * @param storeColumn the column definition
     * @param value the value to be converted
     * @return the ByteBuffer
     */
    public static ByteBuffer convertValue(Column storeColumn, float value) {
        ByteBuffer result = ByteBuffer.allocateDirect(4);
        result.order(ByteOrder.nativeOrder());
        result.putFloat(value);
        result.flip();
        return result;
    }

    /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
     *
     * @param storeColumn the column definition
     * @param value the value to be converted
     * @return the ByteBuffer
     */
    public static ByteBuffer convertValue(Column storeColumn, byte value) {
        return endianManager.convertValue(storeColumn, value);
    }

    /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
     *
     * @param storeColumn the column definition
     * @param value the value to be converted
     * @return the ByteBuffer
     */
    public static ByteBuffer convertValue(Column storeColumn, short value) {
        return endianManager.convertValue(storeColumn, value);
    }

    /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
     *
     * @param storeColumn the column definition
     * @param value the value to be converted
     * @return the ByteBuffer
     */
    public static ByteBuffer convertValue(Column storeColumn, int value) {
        return endianManager.convertValue(storeColumn, value);
    }

    /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
     *
     * @param storeColumn the column definition
     * @param value the value to be converted
     * @return the ByteBuffer
     */
    public static ByteBuffer convertValue(Column storeColumn, long value) {
        return endianManager.convertValue(storeColumn, value);
    }

    /** Encode a String as a ByteBuffer that can be passed to ndbjtie.
     * Put the length information in the beginning of the buffer.
     * Pad fixed length strings with blanks.
     * @param storeColumn the column definition
     * @param value the value to be converted
     * @return the ByteBuffer
     */
    protected static ByteBuffer convertValue(Column storeColumn, String value) {
        if (value == null) {
            value = "";
        }
        CharSequence chars = value;
        int offset = storeColumn.getPrefixLength();
        if (offset == 0) {
            chars = padString(value, storeColumn);
        }
        ByteBuffer byteBuffer = encodeToByteBuffer(chars, storeColumn.getCharsetNumber(), offset);
        fixBufferPrefixLength(storeColumn.getName(), byteBuffer, offset);
        if (logger.isDetailEnabled()) dumpBytesToLog(byteBuffer, byteBuffer.limit());
        return byteBuffer;
    }

    /** Encode a String as a ByteBuffer that can be passed to ndbjtie in a COND_LIKE filter.
     * There is no length information in the beginning of the buffer.
     * @param storeColumn the column definition
     * @param value the value to be converted
     * @return the ByteBuffer
     */
    protected static ByteBuffer convertValueForLikeFilter(Column storeColumn, String value) {
        if (value == null) {
            value = "";
        }
        CharSequence chars = value;
        ByteBuffer byteBuffer = encodeToByteBuffer(chars, storeColumn.getCharsetNumber(), 0);
        if (logger.isDetailEnabled()) dumpBytesToLog(byteBuffer, byteBuffer.limit());
        return byteBuffer;
    }

    /** Encode a byte[] as a ByteBuffer that can be passed to ndbjtie in a COND_LIKE filter.
     * There is no length information in the beginning of the buffer.
     * @param storeColumn the column definition
     * @param value the value to be converted
     * @return the ByteBuffer
     */
    protected static ByteBuffer convertValueForLikeFilter(Column storeColumn, byte[] value) {
        if (value == null) {
            value = EMPTY_BYTE_ARRAY;
        }
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(value.length);
        byteBuffer.put(value);
        byteBuffer.flip();
        if (logger.isDetailEnabled()) dumpBytesToLog(byteBuffer, byteBuffer.limit());
        return byteBuffer;
    }

    /** Pad the value with blanks on the right.
     * @param value the input value
     * @param storeColumn the store column
     * @return the value padded with blanks on the right
     */
    private static CharSequence padString(CharSequence value, Column storeColumn) {
        CharSequence chars = value;
        int suppliedLength = value.length();
        int requiredLength = storeColumn.getColumnSpace();
        if (suppliedLength > requiredLength) {
            throw new ClusterJUserException(local.message("ERR_Data_Too_Long",
                    storeColumn.getName(), requiredLength, suppliedLength));
        } else if (suppliedLength < requiredLength) {
            // pad to fixed length
            StringBuilder buffer = new StringBuilder(requiredLength);
            buffer.append(value);
            buffer.append(SPACE_PAD, 0, requiredLength - suppliedLength);
            chars = buffer;
        }
        return chars;
    }

    /** Fix the length information in a buffer based on the length prefix,
     * either 0, 1, or 2 bytes that hold the length information.
     * @param byteBuffer the byte buffer to fix
     * @param offset the size of the length prefix
     */
    public static void fixBufferPrefixLength(String columnName, ByteBuffer byteBuffer, int offset) {
        int limit = byteBuffer.limit();
        // go back and fill in the length field(s)
        // size of the output char* is current limit minus offset
        int length = limit - offset;
        byteBuffer.position(0);
        switch (offset) {
            case 0:
                break;
            case 1:
                if (length > 255) {
                    throw new ClusterJUserException(local.message("ERR_Varchar_Too_Big",
                            length, columnName));
                }
                byteBuffer.put((byte)(length % 256));
                break;
            case 2:
                if (length > 65535) {
                    throw new ClusterJUserException(local.message("ERR_Varchar_Too_Big",
                            length, columnName));
                }
                byteBuffer.put((byte)(length % 256));
                byteBuffer.put((byte)(length / 256));
                break;
        }
        // reset the position and limit for return
        byteBuffer.position(0);
        byteBuffer.limit(limit);
    }

    /** Pack milliseconds since the Epoch into an int in database Date format.
     * The date is converted into a three-byte value encoded as
     * YYYYx16x32 + MMx32 + DD.
     * Add one to the month since Calendar month is 0-origin.
     * @param millis milliseconds since the Epoch
     * @return the int in packed Date format
     */
    private static int packDate(long millis) {
        Calendar calendar = Calendar.getInstance();
        calendar.clear();
        calendar.setTimeInMillis(millis);
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);
        int day = calendar.get(Calendar.DATE);
        int date = (year * 512) + ((month + 1) * 32) + day;
        return date;
    }

    /** Pack milliseconds since the Epoch into an int in database Time format.
     * Subtract one from date to get number of days (date is 1 origin).
     * The time is converted into a three-byte value encoded as
     * DDx240000 + HHx10000 + MMx100 + SS.
     * @param millis milliseconds since the Epoch
     * @return the int in packed Time format
     */
    private static int packTime(long millis) {
        Calendar calendar = Calendar.getInstance();
        calendar.clear();
        calendar.setTimeInMillis(millis);
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);
        int day = calendar.get(Calendar.DATE);
        int hour = calendar.get(Calendar.HOUR);
        int minute = calendar.get(Calendar.MINUTE);
        int second = calendar.get(Calendar.SECOND);
        if (month != 0) {
            throw new ClusterJUserException(
                    local.message("ERR_Write_Time_Domain", new java.sql.Time(millis), millis, year, month, day, hour, minute, second));
        }
        int time = ((day - 1) * 240000) + (hour * 10000) + (minute * 100) + second;
        return time;
    }

    /** Pack milliseconds since the Epoch into a long in database Datetime format.
     * The Datetime contains a eight-byte date and time packed as
     * YYYYx10000000000 + MMx100000000 + DDx1000000 + HHx10000 + MMx100 + SS
     * Calendar month is 0 origin so add 1 to get packed month
     * @param value milliseconds since the Epoch
     * @return the long in packed Datetime format
     */
    protected static long packDatetime(long value) {
        Calendar calendar = Calendar.getInstance();
        calendar.clear();
        calendar.setTimeInMillis(value);
        long year = calendar.get(Calendar.YEAR);
        long month = calendar.get(Calendar.MONTH) + 1;
        long day = calendar.get(Calendar.DATE);
        long hour = calendar.get(Calendar.HOUR);
        long minute = calendar.get(Calendar.MINUTE);
        long second = calendar.get(Calendar.SECOND);
        long packedDatetime = (year * 10000000000L) + (month * 100000000L) + (day * 1000000L)
                + (hour * 10000L) + (minute * 100) + second;
        return packedDatetime;
    }

    /** Convert the byte[] into a String to be used for logging and debugging.
     *
     * @param bytes the byte[] to be dumped
     * @return the String representation
     */
    public static String dumpBytes (byte[] bytes) {
        StringBuffer buffer = new StringBuffer("byte[");
        buffer.append(bytes.length);
        buffer.append("]: [");
        for (int i = 0; i < bytes.length; ++i) {
            buffer.append((int)bytes[i]);
            buffer.append(" ");
        }
        buffer.append("]");
        return buffer.toString();
    }

    /** Convert the byteBuffer into a String to be used for logging and debugging.
     *
     * @param byteBuffer the byteBuffer to be dumped
     * @return the String representation
     */
    public static String dumpBytes(ByteBuffer byteBuffer) {
        byteBuffer.mark();
        int length = byteBuffer.limit() - byteBuffer.position();
        byte[] dst = new byte[length];
        byteBuffer.get(dst);
        byteBuffer.reset();
        return dumpBytes(dst);
    }

    private static void dumpBytesToLog(ByteBuffer byteBuffer, int limit) {
        StringBuffer message = new StringBuffer("String position is: ");
        message.append(byteBuffer.position());
        message.append(" limit: ");
        message.append(byteBuffer.limit());
        message.append(" data [");
        while (byteBuffer.hasRemaining()) {
            message.append((int)byteBuffer.get());
            message.append(" ");
        }
        message.append("]");
        logger.detail(message.toString());
        byteBuffer.position(0);
        byteBuffer.limit(limit);
    }

    public static BigDecimal getDecimal(ByteBuffer byteBuffer, int length, int precision, int scale) {
        String decimal = null;
        try {
            decimal = getDecimalString(byteBuffer, length, precision, scale);
            return new BigDecimal(decimal);
        } catch (NumberFormatException nfe) {
            throw new ClusterJUserException(
                    local.message("ERR_Number_Format", decimal, dump(decimal)));
        }
    }
  
    public static BigInteger getBigInteger(ByteBuffer byteBuffer, int length, int precision, int scale) {
        String decimal = null;
        try {
            decimal = getDecimalString(byteBuffer, length, precision, scale);
            return new BigInteger(decimal);
        } catch (NumberFormatException nfe) {
            throw new ClusterJUserException(
                    local.message("ERR_Number_Format", decimal, dump(decimal)));
        }
    }
  
    /** Get a Decimal String from the byte buffer.
     *
     * @param byteBuffer the byte buffer with the raw data, starting at position()
     * @param length the length of the data
     * @param precision the precision of the data
     * @param scale the scale of the data
     * @return the Decimal String representation of the value
     */
    public static String getDecimalString(ByteBuffer byteBuffer, int length, int precision, int scale) {
        // allow for decimal point and sign and one more for trailing null
        int capacity = precision + 3;
        ByteBuffer digits = ByteBuffer.allocateDirect(capacity);
        int returnCode = Utils.decimal_bin2str(byteBuffer, length, precision, scale, digits, capacity);
        if (returnCode != 0) {
            throw new ClusterJUserException(
                    local.message("ERR_Binary_Decimal_To_String",
                    returnCode, precision, scale, dumpBytes(byteBuffer)));
        }
        String string = null;
        // look for the end (null) of the result string
        for (int i = 0; i < digits.limit(); ++i) {
            if (digits.get(i) == 0) {
                // found the end; mark it so we only decode the answer characters
                digits.limit(i);
                break;
            }
        }
        try {
            // use basic decoding
            CharBuffer charBuffer = charsetDecoder.decode(digits);
            string = charBuffer.toString();
            return string;
        } catch (CharacterCodingException e) {
            throw new ClusterJFatalInternalException(
                    local.message("ERR_Character_Encoding", string));
        }
       
    }

    /** Unpack a Date from its packed int representation.
     * Date is a three-byte integer packed as YYYYx16x32 + MMx32 + DD
     * @param packedDate the packed representation
     * @return the long value as milliseconds since the Epoch
     */
    public static long unpackDate(int packedDate) {
        int date = packedDate & 0x1f;
        packedDate = packedDate >>> 5;
        int month = (packedDate & 0x0f) - 1; // Month value is 0-based. e.g., 0 for January.
        int year = packedDate >>> 4;
        Calendar calendar = Calendar.getInstance();
        calendar.clear();
        calendar.set(year, month, date);
        return calendar.getTimeInMillis();
    }

    /** Unpack a Time from its packed int representation.
     * Time is a three-byte integer packed as DDx240000 + HHx10000 + MMx100 + SS
     * @param packedTime the packed representation
     * @return the long value as milliseconds since the Epoch
     */
    public static long unpackTime(int packedTime) {
        int second = packedTime % 100;
        packedTime /= 100;
        int minute = packedTime % 100;
        packedTime /= 100;
        int hour = packedTime % 24;
        int date = (packedTime / 24) + 1;
        if (date > 31) {
            throw new ClusterJUserException(
                    local.message("ERR_Read_Time_Domain", packedTime, date, hour, minute, second));
        }
        Calendar calendar = Calendar.getInstance();
        calendar.clear();
        calendar.set(Calendar.DATE, date);
        calendar.set(Calendar.HOUR, hour);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, second);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar.getTimeInMillis();
    }

    /** Unpack a Datetime from its packed long representation.
     * The Datetime contains a long packed as
     * YYYYx10000000000 + MMx100000000 + DDx1000000 + HHx10000 + MMx100 + SS
     * Calendar month is 0 origin so subtract 1 from packed month
     * @param packedDatetime the packed representation
     * @return the value as milliseconds since the Epoch
     */
    protected static long unpackDatetime(long packedDatetime) {
        int second = (int)(packedDatetime % 100);
        packedDatetime /= 100;
        int minute = (int)(packedDatetime % 100);
        packedDatetime /= 100;
        int hour = (int)(packedDatetime % 100);
        packedDatetime /= 100;
        int day = (int)(packedDatetime % 100);
        packedDatetime /= 100;
        int month = (int)(packedDatetime % 100) - 1;
        int year = (int)(packedDatetime / 100);
        Calendar calendar = Calendar.getInstance();
        calendar.clear();
        calendar.set(Calendar.YEAR, year);
        calendar.set(Calendar.MONTH, month);
        calendar.set(Calendar.DATE, day);
        calendar.set(Calendar.HOUR, hour);
        calendar.set(Calendar.MINUTE, minute);
        calendar.set(Calendar.SECOND, second);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar.getTimeInMillis();
       
    }

    /** Decode a byte[] into a String using the charset. The return value
     * is in UTF16 format.
     *
     * @param array the byte[] to be decoded
     * @param collation the collation
     * @return the decoded String
     */
    public static String decode(byte[] array, int collation) {
        if (array == null) return null;
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(array.length);
        byteBuffer.put(array);
        byteBuffer.flip();
        int inputLength = array.length;
        // TODO make this more reasonable
        int outputLength = inputLength * 4;
        ByteBuffer outputByteBuffer = ByteBuffer.allocateDirect(outputLength);
        int[] lengths = new int[] {inputLength, outputLength};
        int returnCode = charsetMap.recode(lengths, collation, collationUTF16,
                byteBuffer, outputByteBuffer);
        switch (returnCode) {
            case CharsetMapConst.RecodeStatus.RECODE_OK:
                outputByteBuffer.limit(lengths[1]);
                CharBuffer charBuffer = outputByteBuffer.asCharBuffer();
                return charBuffer.toString();
            case CharsetMapConst.RecodeStatus.RECODE_BAD_CHARSET:
                throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Charset",
                        collation));
            case CharsetMapConst.RecodeStatus.RECODE_BAD_SRC:
                throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Source",
                        collation, lengths[0]));
            case CharsetMapConst.RecodeStatus.RECODE_BUFF_TOO_SMALL:
                throw new ClusterJFatalInternalException(local.message("ERR_Decode_Buffer_Too_Small",
                        collation, inputLength, outputLength, lengths[0], lengths[1]));
            default:
                throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Return_Code",
                        returnCode));
        }
    }

    /** Decode a ByteBuffer into a String using the charset. The return value
     * is in UTF16 format.
     *
     * @param inputByteBuffer the byte buffer to be decoded
     * @param collation the collation
     * @return the decoded String
     */
    protected static String decode(ByteBuffer inputByteBuffer, int collation) {
        int inputLength = inputByteBuffer.limit() - inputByteBuffer.position();
        int outputLength = inputLength * 2;
        ByteBuffer outputByteBuffer = ByteBuffer.allocateDirect(outputLength);
        int[] lengths = new int[] {inputLength, outputLength};
        int returnCode = charsetMap.recode(lengths, collation, collationUTF16,
                inputByteBuffer, outputByteBuffer);
        switch (returnCode) {
            case CharsetMapConst.RecodeStatus.RECODE_OK:
                outputByteBuffer.limit(lengths[1]);
                CharBuffer charBuffer = outputByteBuffer.asCharBuffer();
                return charBuffer.toString();
            case CharsetMapConst.RecodeStatus.RECODE_BAD_CHARSET:
                throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Charset",
                        collation));
            case CharsetMapConst.RecodeStatus.RECODE_BAD_SRC:
                throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Source",
                        collation, lengths[0]));
            case CharsetMapConst.RecodeStatus.RECODE_BUFF_TOO_SMALL:
                throw new ClusterJFatalInternalException(local.message("ERR_Decode_Buffer_Too_Small",
                        collation, inputLength, outputLength, lengths[0], lengths[1]));
            default:
                throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Return_Code",
                        returnCode));
        }
    }


    /** Encode a String into a byte[] for storage.
     * This is used by character large objects when mapping text columns.
     *
     * @param string the String to encode
     * @param collation the collation
     * @return the encoded byte[]
     */
    public static byte[] encode(String string, int collation) {
        ByteBuffer encoded = encodeToByteBuffer(string, collation, 0);
        int length = encoded.limit();
        byte[] result = new byte[length];
        encoded.get(result);
        return result;
    }

    /** Encode a String into a ByteBuffer
     * using the mysql native encoding method.
     * @param string the String to encode
     * @param collation the collation
     * @param prefixLength the length of the length prefix
     * @return the encoded ByteBuffer with position set to prefixLength
     * and limit one past the last converted byte
     */
    public static ByteBuffer encodeToByteBuffer(CharSequence string, int collation, int prefixLength) {
        if (string == null) return null;
        int inputLength = (string.length() * 2);
        ByteBuffer inputByteBuffer = ByteBuffer.allocateDirect(inputLength);
        CharBuffer charBuffer = inputByteBuffer.asCharBuffer();
        charBuffer.append(string);
        int outputLength = (2 * inputLength) + prefixLength;
        ByteBuffer outputByteBuffer = ByteBuffer.allocateDirect(outputLength);
        outputByteBuffer.position(prefixLength);
        int[] lengths = new int[] {inputLength, outputLength - prefixLength};
        int returnCode = charsetMap.recode(lengths, collationUTF16, collation,
                inputByteBuffer, outputByteBuffer);
       
        switch (returnCode) {
            case CharsetMapConst.RecodeStatus.RECODE_OK:
                outputByteBuffer.limit(prefixLength + lengths[1]);
                return outputByteBuffer;
            case CharsetMapConst.RecodeStatus.RECODE_BAD_CHARSET:
                throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Charset",
                        collation));
            case CharsetMapConst.RecodeStatus.RECODE_BAD_SRC:
                throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Source",
                        collation, lengths[0]));
            case CharsetMapConst.RecodeStatus.RECODE_BUFF_TOO_SMALL:
                throw new ClusterJFatalInternalException(local.message("ERR_Encode_Buffer_Too_Small",
                        collation, inputLength, outputLength, lengths[0], lengths[1]));
            default:
                throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Return_Code",
                        returnCode));
        }
    }

    /** Encode a String into a ByteBuffer for storage.
     *
     * @param input the input String
     * @param storeColumn the store column
     * @param bufferManager the buffer manager with shared buffers
     * @return a byte buffer with prefix length
     */
    public static ByteBuffer encode(String input, Column storeColumn, BufferManager bufferManager) {
        int collation = storeColumn.getCharsetNumber();
        CharsetConverter charsetConverter = getCharsetConverter(collation);
        CharSequence chars = input;
        int prefixLength = storeColumn.getPrefixLength();
        if (prefixLength == 0) {
            chars = padString(input, storeColumn);
        }
        return charsetConverter.encode(storeColumn.getName(), chars, collation, prefixLength, bufferManager);
    }

    /** Decode a ByteBuffer into a String using the charset. The return value
     * is in UTF16 format.
     *
     * @param inputByteBuffer the byte buffer to be decoded positioned past the length prefix
     * @param collation the collation
     * @param bufferManager the buffer manager with shared buffers
     * @return the decoded String
     */
    public static String decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager) {
        CharsetConverter charsetConverter = getCharsetConverter(collation);
        return charsetConverter.decode(inputByteBuffer, collation, bufferManager);
    }

    /** Get the charset converter for the given collation.
     * This is in the inner loop and must be highly optimized for performance.
     * @param collation the collation
     * @return the charset converter for the collation
     */
    private static CharsetConverter getCharsetConverter(int collation) {
        // must be synchronized because the charsetConverters is not synchronized
        // we avoid a race condition where a charset converter is in the process
        // of being created and it's partially visible by another thread in charsetConverters
        synchronized (charsetConverters) {
            if (collation + 1 > charsetConverters.length) {
                // unlikely; only if collations are added beyond existing collation number
                String charsetName = charsetMap.getName(collation);
                logger.warn(local.message("ERR_Charset_Number_Too_Big", collation, charsetName,
                        MAXIMUM_MYSQL_COLLATION_NUMBER));
                return charsetConverterMultibyte;
            }
            CharsetConverter result = charsetConverters[collation];
            if (result == null) {
                result = addCollation(collation);
            }
            return result;
        }
    }

    /** Create a new charset converter and add it to the collection of charset converters
     * for all collations that share the same charset.
     *
     * @param collation the collation to add
     * @return the charset converter for the collation
     */
    private static CharsetConverter addCollation(int collation) {
        if (isMultibyteCollation(collation)) {
            return charsetConverters[collation] = charsetConverterMultibyte;
        }
        String charsetName = charsetMap.getMysqlName(collation);
        CharsetConverter charsetConverter = new SingleByteCharsetConverter(collation);
        int[] collations = collationPeersMap.get(charsetName);
        if (collations == null) {
            // unlikely; only if a new collation is added
            collations = new int[] {collation};
            collationPeersMap.put(charsetName, collations);
            logger.warn(local.message("WARN_Unknown_Collation", collation, charsetName));
            return charsetConverter;
        }
        for (int peer: collations) {
            // for each collation that shares the same charset name, set the charset converter
            logger.info("Adding charset converter " + charsetName + " for collation " + peer);
            charsetConverters[peer] = charsetConverter;
        }
        return charsetConverter;
    }

    /** Is the collation multibyte?
     *
     * @param collation the collation number
     * @return true if the collation uses a multibyte charset; false if the collation uses a single byte charset;
     * and null if the collation is not a valid collation
     */
    private static Boolean isMultibyteCollation(int collation) {
        boolean[] multibyte = charsetMap.isMultibyte(collation);
        return (multibyte == null)?null:multibyte[0];
    }

    /** Utility methods for encoding and decoding Strings.
     */
    protected interface CharsetConverter {

        ByteBuffer encode(String columnName, CharSequence input, int collation, int prefixLength, BufferManager bufferManager);

        String decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager);
    }

    /** Class for encoding and decoding multibyte charsets. A single instance of this class
     * can be shared among all multibyte charsets.
     */
    protected static class MultiByteCharsetConverter implements CharsetConverter {

        /** Encode a String into a ByteBuffer. The input String is copied into a shared byte buffer.
         * The buffer is encoded via the mysql recode method to a shared String storage buffer.
         * If the output buffer is too small, a new buffer is allocated and the encoding is repeated.
         * @param input the input String
         * @param collation the charset number
         * @param prefixLength the prefix length (0, 1, or 2 depending on the type)
         * @param bufferManager the buffer manager with shared buffers
         * @return a byte buffer positioned at zero with the data ready to send to the database
         */
        public ByteBuffer encode(String columnName, CharSequence input, int collation, int prefixLength, BufferManager bufferManager) {
            // input length in bytes is twice String length
            int inputLength = input.length() * 2;
            ByteBuffer inputByteBuffer = bufferManager.copyStringToByteBuffer(input);
            boolean done = false;
            // first try with output length equal input length
            int sizeNeeded = inputLength;
            while (!done) {
                ByteBuffer outputByteBuffer = bufferManager.getStringStorageBuffer(sizeNeeded);
                int outputLength = outputByteBuffer.limit();
                outputByteBuffer.position(prefixLength);
                int[] lengths = new int[] {inputLength, outputLength - prefixLength};
                int returnCode = charsetMap.recode(lengths, collationUTF16, collation,
                        inputByteBuffer, outputByteBuffer);
                switch (returnCode) {
                    case CharsetMapConst.RecodeStatus.RECODE_OK:
                        outputByteBuffer.limit(prefixLength + lengths[1]);
                        outputByteBuffer.position(0);
                        fixBufferPrefixLength(columnName, outputByteBuffer, prefixLength);
                        return outputByteBuffer;
                    case CharsetMapConst.RecodeStatus.RECODE_BAD_CHARSET:
                        throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Charset",
                                collation));
                    case CharsetMapConst.RecodeStatus.RECODE_BAD_SRC:
                        throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Source",
                                collation, lengths[0]));
                    case CharsetMapConst.RecodeStatus.RECODE_BUFF_TOO_SMALL:
                        // loop increasing output buffer size until success or run out of memory...
                        sizeNeeded = sizeNeeded * 3 / 2;
                        break;
                    default:
                        throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Return_Code",
                                returnCode));
                }
            }
            return null; // to make compiler happy; we never get here
        }

        /** Decode a byte buffer into a String. The input is decoded by the mysql charset recode method
         * into a shared buffer. Then the shared buffer is used to create the result String.
         * The input byte buffer is positioned just past the length, and its limit is set to one past the
         * characters to decode.
         * @param inputByteBuffer the input byte buffer
         * @param collation the charset number
         * @param bufferManager the buffer manager with shared buffers
         * @return the decoded String
         */
        public String decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager) {
            int inputLength = inputByteBuffer.limit() - inputByteBuffer.position();
            int sizeNeeded = inputLength * 4;
            boolean done = false;
            while (!done) {
                ByteBuffer outputByteBuffer = bufferManager.getStringByteBuffer(sizeNeeded);
                CharBuffer outputCharBuffer = bufferManager.getStringCharBuffer();
                int outputLength = outputByteBuffer.capacity();
                outputByteBuffer.position(0);
                outputByteBuffer.limit(outputLength);
                int[] lengths = new int[] {inputLength, outputLength};
                int returnCode = charsetMap.recode(lengths, collation, collationUTF16,
                        inputByteBuffer, outputByteBuffer);
                switch (returnCode) {
                    case CharsetMapConst.RecodeStatus.RECODE_OK:
                        outputCharBuffer.position(0);
                        // each output character is two bytes for UTF16
                        outputCharBuffer.limit(lengths[1] / 2);
                        return outputCharBuffer.toString();
                    case CharsetMapConst.RecodeStatus.RECODE_BAD_CHARSET:
                        throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Charset",
                                collation));
                    case CharsetMapConst.RecodeStatus.RECODE_BAD_SRC:
                        throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Source",
                                collation, lengths[0]));
                    case CharsetMapConst.RecodeStatus.RECODE_BUFF_TOO_SMALL:
                        // try a bigger buffer
                        sizeNeeded = sizeNeeded * 3 / 2;
                        break;
                    default:
                        throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Return_Code",
                                returnCode));
                }
            }
            return null; // never reached; make the compiler happy
        }
    }

    /** Class for encoding and decoding single byte collations.
     */
    protected static class SingleByteCharsetConverter implements CharsetConverter {

        private static final int BYTE_RANGE = (1 + Byte.MAX_VALUE) - Byte.MIN_VALUE;
        private static byte[] allBytes = new byte[BYTE_RANGE];
        // The initial charToByteMap, with all char mappings mapped
        // to (byte) '?', so that unknown characters are mapped to '?'
        // instead of '\0' (which means end-of-string to MySQL).
        private static byte[] unknownCharsMap = new byte[65536];

        static {
            // initialize allBytes with all possible byte values
            for (int i = Byte.MIN_VALUE; i <= Byte.MAX_VALUE; i++) {
                allBytes[i - Byte.MIN_VALUE] = (byte) i;
            }
            // initialize unknownCharsMap to '?' in each position
            for (int i = 0; i < unknownCharsMap.length; i++) {
                unknownCharsMap[i] = (byte) '?'; // use something 'sane' for unknown chars
            }
        }

        /** The byte to char array */
        private char[] byteToChars = new char[BYTE_RANGE];

        /** The char to byte array */
        private byte[] charToBytes = new byte[65536];

        /** Construct a new single byte charset converter. This converter is only used for
         * charsets that encode to a single byte for any input character.
         * @param collation
         */
        public SingleByteCharsetConverter(int collation) {
            ByteBuffer allBytesByteBuffer = ByteBuffer.allocateDirect(256);
            allBytesByteBuffer.put(allBytes);
            allBytesByteBuffer.flip();
            String allBytesString = Utility.decode(allBytesByteBuffer, collation);
            if (allBytesString.length() != 256) {
                String charsetName = charsetMap.getName(collation);
                throw new ClusterJFatalInternalException(local.message("ERR_Bad_Charset_Decode_All_Chars",
                        collation, charsetName, allBytesString.length()));
            }
            int allBytesLen = allBytesString.length();

            System.arraycopy(unknownCharsMap, 0, this.charToBytes, 0,
                    this.charToBytes.length);

            for (int i = 0; i < BYTE_RANGE && i < allBytesLen; i++) {
                char c = allBytesString.charAt(i);
                this.byteToChars[i] = c;
                this.charToBytes[c] = allBytes[i];
            }
        }

        /** Encode a String into a ByteBuffer. The input String is encoded, character by character,
         * into an output byte[]. Then the output is copied into a shared byte buffer.
         * @param input the input String
         * @param collation the charset number
         * @param prefixLength the prefix length (0, 1, or 2 depending on the type)
         * @param bufferManager the buffer manager with shared buffers
         * @return a byte buffer positioned at zero with the data ready to send to the database
         */
        public ByteBuffer encode(String columnName, CharSequence input, int collation, int prefixLength, BufferManager bufferManager) {
            int length = input.length();
            byte[] outputBytes = new byte[length];
            for (int position = 0; position < length; ++position) {
                outputBytes[position] = charToBytes[input.charAt(position)];
            }
            // input is now encoded; copy to shared output buffer
            ByteBuffer outputByteBuffer = bufferManager.getStringStorageBuffer(length + prefixLength);
            // skip over prefix
            outputByteBuffer.position(prefixLength);
            outputByteBuffer.put(outputBytes);
            outputByteBuffer.flip();
            // adjust the length prefix
            fixBufferPrefixLength(columnName, outputByteBuffer, prefixLength);
            return outputByteBuffer;
        }

        /** Decode a byte buffer into a String. The input byte buffer is copied into a byte[],
         * then encoded byte by byte into an output char[]. Then the result String is created from the char[].
         * The input byte buffer is positioned just past the length, and its limit is set to one past the
         * characters to decode.
         * @param inputByteBuffer the input byte buffer
         * @param collation the charset number
         * @param bufferManager the buffer manager with shared buffers
         * @return the decoded String
         */
        public String decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager) {
            int inputLimit = inputByteBuffer.limit();
            int inputPosition = inputByteBuffer.position();
            int inputSize = inputLimit- inputPosition;
            byte[] inputBytes = new byte[inputSize];
            inputByteBuffer.get(inputBytes);
            char[] outputChars = new char[inputSize];
            for (int position = 0; position < inputSize; ++position) {
                outputChars[position] = byteToChars[inputBytes[position] - Byte.MIN_VALUE];
            }
            // input is now decoded; create a new String from the output
            String result = new String(outputChars);
            return result;
        }
    }

    private static String dump(String string) {
        StringBuffer buffer = new StringBuffer("[");
        for (int i = 0; i < string.length(); ++i) {
            int theCharacter = string.charAt(i);
            buffer.append(theCharacter);
            buffer.append(" ");
        }
        buffer.append("]");
        return buffer.toString();
    }

    /** For each group of 9 decimal digits, the number of bytes needed
     * to represent that group of digits:
     * 10, 100 -> 1; 256
     * 1,000, 10,000 -> 2; 65536
     * 100,000, 1,000,000 -> 3 16,777,216
     * 10,000,000, 100,000,000, 1,000,000,000 -> 4
     */
    static int[] howManyBytesNeeded = new int[] {0112233444,
                                                     556677888,
                                                     99, 10, 10, 11, 11, 12, 12, 12,
                                                    13, 13, 14, 14, 15, 15, 16, 16, 16,
                                                    17, 17, 18, 18, 19, 19, 20, 20, 20,
                                                    21, 21, 22, 22, 23, 23, 24, 24, 24,
                                                    25, 25, 26, 26, 27, 27, 28, 28, 28,
                                                    29, 29};
    /** Get the number of bytes needed in memory to represent the decimal number.
     *
     * @param precision the precision of the number
     * @param scale the scale
     * @return the number of bytes needed for the binary representation of the number
     */
    public static int getDecimalColumnSpace(int precision, int scale) {
        int howManyBytesNeededForIntegral = howManyBytesNeeded[precision - scale];
        int howManyBytesNeededForFraction = howManyBytesNeeded[scale];
        int result = howManyBytesNeededForIntegral + howManyBytesNeededForFraction;
        return result;
    }

    /** Get a boolean from this ndbRecAttr.
     *
     * @param storeColumn the Column
     * @param ndbRecAttr the NdbRecAttr
     * @return the boolean
     */
    public static boolean getBoolean(Column storeColumn, NdbRecAttr ndbRecAttr) {
        return endianManager.getBoolean(storeColumn, ndbRecAttr);
    }

    /** Get a byte from this ndbRecAttr.
     *
     * @param storeColumn the Column
     * @param ndbRecAttr the NdbRecAttr
     * @return the byte
     */
    public static byte getByte(Column storeColumn, NdbRecAttr ndbRecAttr) {
        return endianManager.getByte(storeColumn, ndbRecAttr);
    }

    /** Get a short from this ndbRecAttr.
     *
     * @param storeColumn the Column
     * @param ndbRecAttr the NdbRecAttr
     * @return the short
     */
    public static short getShort(Column storeColumn, NdbRecAttr ndbRecAttr) {
        return endianManager.getShort(storeColumn, ndbRecAttr);
    }

    /** Get an int from this ndbRecAttr.
     *
     * @param storeColumn the Column
     * @param ndbRecAttr the NdbRecAttr
     * @return the int
     */
    public static int getInt(Column storeColumn, NdbRecAttr ndbRecAttr) {
        return endianManager.getInt(storeColumn, ndbRecAttr);
    }

    /** Get a long from this ndbRecAttr.
     *
     * @param storeColumn the Column
     * @param ndbRecAttr the NdbRecAttr
     * @return the long
     */
    public static long getLong(Column storeColumn, NdbRecAttr ndbRecAttr) {
        return endianManager.getLong(storeColumn, ndbRecAttr);
    }

    /** Convert a long value into a long for storage. The value parameter
     * may be a date (milliseconds since the epoch), a bit array, or simply a long value.
     * The storage format depends on the type of the column and the endian-ness of
     * the host.
     * @param storeColumn the column
     * @param value the java value
     * @return the storage value
     */
    public static long convertLongValueForStorage(Column storeColumn, long value) {
        return endianManager.convertLongValueForStorage(storeColumn, value);
    }

    /** Convert a byte value into an int for storage. The value parameter
     * may be a bit, a bit array (BIT(1..8) needs to be stored as an int) or a byte value.
     * The storage format depends on the type of the column and the endian-ness of
     * the host.
     * @param storeColumn the column
     * @param value the java value
     * @return the storage value
     */
    public static int convertByteValueForStorage(Column storeColumn, byte value) {
        return endianManager.convertByteValueForStorage(storeColumn, value);
    }

    /** Convert a short value into an int for storage. The value parameter
     * may be a bit array (BIT(1..16) needs to be stored as an int) or a short value.
     * The storage format depends on the type of the column and the endian-ness of
     * the host.
     * @param storeColumn the column
     * @param value the java value
     * @return the storage value
     */
    public static int convertShortValueForStorage(Column storeColumn,
            short value) {
        return endianManager.convertShortValueForStorage(storeColumn, value);
    }

}
TOP

Related Classes of com.mysql.clusterj.tie.Utility$SingleByteCharsetConverter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.