com.panet.imeta.core.row.ValueDataUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.panet.imeta.core.row.ValueDataUtil.java

Source

/*
 * Copyright (c) 2007 Pentaho Corporation.  All rights reserved. 
 * This software was developed by Pentaho Corporation and is provided under the terms 
 * of the GNU Lesser General Public License, Version 2.1. You may not use 
 * this file except in compliance with the license. If you need a copy of the license, 
 * please go to http://www.gnu.org/licenses/lgpl-2.1.txt. The Original Code is Pentaho 
 * Data Integration.  The Initial Developer is Pentaho Corporation.
 *
 * Software distributed under the GNU Lesser Public License is distributed on an "AS IS" 
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or  implied. Please refer to 
 * the license for the specific language governing your rights and limitations.
*/
package com.panet.imeta.core.row;

import java.io.FileInputStream;
import java.math.BigDecimal;
import java.security.MessageDigest;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.zip.Adler32;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;

import org.apache.commons.codec.language.DoubleMetaphone;
import org.apache.commons.codec.language.Metaphone;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.provider.local.LocalFile;

import com.panet.imeta.core.Const;
import com.panet.imeta.core.exception.KettleValueException;
import com.panet.imeta.core.vfs.KettleVFS;

public class ValueDataUtil {
    /**
     * @deprecated Use {@link Const#ltrim(String)} instead
     */
    public static final String leftTrim(String string) {
        return Const.ltrim(string);
    }

    /**
     * @deprecated Use {@link Const#rtrim(String)} instead
     */
    public static final String rightTrim(String string) {
        return Const.rtrim(string);
    }

    /**
     * Determines whether or not a character is considered a space.
     * A character is considered a space in Kettle if it is a space, a tab, a newline or a cariage return.
     * @param c The character to verify if it is a space.
     * @return true if the character is a space. false otherwise. 
     * @deprecated Use {@link Const#isSpace(char)} instead
     */
    public static final boolean isSpace(char c) {
        return Const.isSpace(c);
    }

    /**
     * Trims a string: removes the leading and trailing spaces of a String.
     * @param string The string to trim
     * @return The trimmed string.
     * @deprecated Use {@link Const#trim(String)} instead
     */
    public static final String trim(String string) {
        return Const.trim(string);
    }

    /**Levenshtein distance (LD) is a measure of the similarity between two strings, 
     * which we will refer to as the source string (s) and the target string (t). 
     * The distance is the number of deletions, insertions, or substitutions required to transform s into t. 
     */
    public static Long getLevenshtein_Distance(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB,
            Object dataB) {
        if (dataA == null || dataB == null)
            return null;
        return new Long(StringUtils.getLevenshteinDistance(dataA.toString(), dataB.toString()));
    }

    public static String get_Metaphone(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null)
            return null;
        return (new Metaphone()).metaphone(dataA.toString());
    }

    public static String get_Double_Metaphone(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null)
            return null;
        return (new DoubleMetaphone()).doubleMetaphone(dataA.toString());
    }

    public static String initCap(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null)
            return null;
        return Const.initCap(dataA.toString());
    }

    public static String upperCase(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null)
            return null;
        return dataA.toString().toUpperCase();
    }

    public static String lowerCase(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null)
            return null;
        return dataA.toString().toLowerCase();
    }

    public static String maskXML(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null)
            return null;
        return Const.maskXML(dataA.toString());
    }

    public static String useCDATA(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null)
            return null;
        return "<![CDATA[" + dataA.toString() + "]]>";

    }

    public static String removeCR(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null)
            return null;
        return Const.removeCR(dataA.toString());
    }

    public static String removeLF(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null)
            return null;
        return Const.removeLF(dataA.toString());
    }

    public static String removeCRLF(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null)
            return null;
        return Const.removeCRLF(dataA.toString());
    }

    public static String removeTAB(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null)
            return null;
        return Const.removeTAB(dataA.toString());
    }

    public static String getDigits(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null)
            return null;
        return Const.getDigitsOnly(dataA.toString());
    }

    public static String removeDigits(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null)
            return null;
        return Const.removeDigits(dataA.toString());
    }

    public static long stringLen(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null)
            return 0;
        return dataA.toString().length();
    }

    public static String createChecksum(ValueMetaInterface metaA, Object dataA, String type) {
        String md5Hash = null;
        FileInputStream in = null;
        try {
            in = new FileInputStream(dataA.toString());
            int bytes = in.available();
            byte[] buffer = new byte[bytes];
            in.read(buffer);

            StringBuffer md5HashBuff = new StringBuffer(32);
            byte[] b = MessageDigest.getInstance(type).digest(buffer);
            int len = b.length;
            for (int x = 0; x < len; x++) {
                md5HashBuff.append(String.format("%02x", b[x]));
            }

            md5Hash = md5HashBuff.toString();

        } catch (Exception e) {
        } finally {
            try {
                if (in != null)
                    in.close();
            } catch (Exception e) {
            }
            ;
        }

        return md5Hash;
    }

    public static Long ChecksumCRC32(ValueMetaInterface metaA, Object dataA) {
        long checksum = 0;
        FileObject file = null;
        try {
            file = KettleVFS.getFileObject(dataA.toString());
            CheckedInputStream cis = null;

            // Computer CRC32 checksum
            cis = new CheckedInputStream((FileInputStream) ((LocalFile) file).getInputStream(), new CRC32());
            byte[] buf = new byte[128];
            while (cis.read(buf) >= 0) {
            }

            checksum = cis.getChecksum().getValue();

        } catch (Exception e) {
        } finally {
            if (file != null)
                try {
                    file.close();
                } catch (Exception e) {
                }
            ;
        }
        return checksum;
    }

    public static Long ChecksumAdler32(ValueMetaInterface metaA, Object dataA) {
        long checksum = 0;
        FileObject file = null;
        try {
            file = KettleVFS.getFileObject(dataA.toString());
            CheckedInputStream cis = null;

            // Computer Adler-32 checksum
            cis = new CheckedInputStream((FileInputStream) ((LocalFile) file).getInputStream(), new Adler32());

            byte[] buf = new byte[128];
            while (cis.read(buf) >= 0) {
            }
            checksum = cis.getChecksum().getValue();

        } catch (Exception e) {
            //throw new Exception(e);
        } finally {
            if (file != null)
                try {
                    file.close();
                } catch (Exception e) {
                }
            ;
        }
        return checksum;
    }

    public static Object plus(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {
        if (dataA == null || dataB == null)
            return null;

        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_STRING:
            return metaA.getString(dataA) + metaB.getString(dataB);
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(metaA.getNumber(dataA).doubleValue() + metaB.getNumber(dataB).doubleValue());
        case ValueMetaInterface.TYPE_INTEGER:
            return new Long(metaA.getInteger(dataA).longValue() + metaB.getInteger(dataB).longValue());
        case ValueMetaInterface.TYPE_BOOLEAN:
            return Boolean
                    .valueOf(metaA.getBoolean(dataA).booleanValue() || metaB.getBoolean(dataB).booleanValue());
        case ValueMetaInterface.TYPE_BIGNUMBER:
            return metaA.getBigNumber(dataA).add(metaB.getBigNumber(dataB));

        default:
            throw new KettleValueException("The 'plus' function only works on numeric data and Strings.");
        }
    }

    public static Object plus3(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB,
            ValueMetaInterface metaC, Object dataC) throws KettleValueException {
        if (dataA == null || dataB == null || dataC == null)
            return null;

        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_STRING:
            return metaA.getString(dataA) + metaB.getString(dataB) + metaC.getString(dataC);
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(metaA.getNumber(dataA).doubleValue() + metaB.getNumber(dataB).doubleValue()
                    + metaC.getNumber(dataC).doubleValue());
        case ValueMetaInterface.TYPE_INTEGER:
            return new Long(metaA.getInteger(dataA).longValue() + metaB.getInteger(dataB).longValue()
                    + metaC.getInteger(dataC).longValue());
        case ValueMetaInterface.TYPE_BOOLEAN:
            return Boolean.valueOf(metaA.getBoolean(dataA).booleanValue() || metaB.getBoolean(dataB).booleanValue()
                    || metaB.getBoolean(dataC).booleanValue());
        case ValueMetaInterface.TYPE_BIGNUMBER:
            return metaA.getBigNumber(dataA).add(metaB.getBigNumber(dataB).add(metaC.getBigNumber(dataC)));

        default:
            throw new KettleValueException("The 'plus' function only works on numeric data and Strings.");
        }
    }

    public static Object sum(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {
        if (dataA == null && dataB == null)
            return null;
        if (dataA == null && dataB != null)
            return metaA.convertData(metaB, dataB);
        if (dataA != null && dataB == null)
            return dataA;

        return plus(metaA, dataA, metaB, dataB);
    }

    public static Object loadFileContentInBinary(ValueMetaInterface metaA, Object dataA)
            throws KettleValueException {
        if (dataA == null)
            return null;

        FileObject file = null;
        FileInputStream fis = null;
        try {
            file = KettleVFS.getFileObject(dataA.toString());
            fis = (FileInputStream) ((LocalFile) file).getInputStream();
            int fileSize = (int) file.getContent().getSize();
            byte[] content = Const.createByteArray(fileSize);
            fis.read(content, 0, fileSize);
            return content;
        } catch (Exception e) {
            throw new KettleValueException(e);
        } finally {
            try {
                if (file != null)
                    file.close();
                if (fis != null)
                    fis.close();
            } catch (Exception e) {
            }
            ;
        }
    }

    public static Object minus(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {
        if (dataA == null || dataB == null)
            return null;

        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(metaA.getNumber(dataA).doubleValue() - metaB.getNumber(dataB).doubleValue());
        case ValueMetaInterface.TYPE_INTEGER:
            return new Long(metaA.getInteger(dataA).longValue() - metaB.getInteger(dataB).longValue());
        case ValueMetaInterface.TYPE_BIGNUMBER:
            return metaA.getBigNumber(dataA).subtract(metaB.getBigNumber(dataB));
        case ValueMetaInterface.TYPE_DATE:
            return new Long(metaA.getInteger(dataA).longValue() - metaB.getInteger(dataB).longValue());

        default:
            throw new KettleValueException("The 'minus' function only works on numeric data.");
        }
    }

    public static Object multiply(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {
        if (dataA == null || dataB == null)
            return null;

        if ((metaB.isString() && metaA.isNumeric()) || (metaB.isNumeric() && metaA.isString())) {
            StringBuffer s;
            String append = "";
            int n;
            if (metaB.isString()) {
                s = new StringBuffer(metaB.getString(dataB));
                append = metaB.getString(dataB);
                n = metaA.getInteger(dataA).intValue();
            } else {
                s = new StringBuffer(metaA.getString(dataA));
                append = metaA.getString(dataA);
                n = metaB.getInteger(dataB).intValue();
            }

            if (n == 0)
                s.setLength(0);
            else
                for (int i = 1; i < n; i++)
                    s.append(append);

            return s.toString();
        }

        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(metaA.getNumber(dataA).doubleValue() * metaB.getNumber(dataB).doubleValue());
        case ValueMetaInterface.TYPE_INTEGER:
            return new Long(metaA.getInteger(dataA).longValue() * metaB.getInteger(dataB).longValue());
        case ValueMetaInterface.TYPE_BIGNUMBER:
            return metaA.getBigNumber(dataA).multiply(metaB.getBigNumber(dataB));

        default:
            throw new KettleValueException(
                    "The 'multiply' function only works on numeric data optionally multiplying strings.");
        }
    }

    public static Object divide(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {
        if (dataA == null || dataB == null)
            return null;

        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(metaA.getNumber(dataA).doubleValue() / metaB.getNumber(dataB).doubleValue());
        case ValueMetaInterface.TYPE_INTEGER:
            return new Long(metaA.getInteger(dataA).longValue() / metaB.getInteger(dataB).longValue());
        case ValueMetaInterface.TYPE_BIGNUMBER:
            return metaA.getBigNumber(dataA).divide(metaB.getBigNumber(dataB), BigDecimal.ROUND_HALF_UP);

        default:
            throw new KettleValueException("The 'divide' function only works on numeric data.");
        }
    }

    public static Object sqrt(ValueMetaInterface metaA, Object dataA) throws KettleValueException {
        if (dataA == null)
            return null;

        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(Math.sqrt(metaA.getNumber(dataA).doubleValue()));
        case ValueMetaInterface.TYPE_INTEGER:
            return new Long(Math.round(Math.sqrt(metaA.getNumber(dataA).doubleValue())));
        case ValueMetaInterface.TYPE_BIGNUMBER:
            return BigDecimal.valueOf(Math.sqrt(metaA.getNumber(dataA).doubleValue()));

        default:
            throw new KettleValueException(
                    "The 'multiply' function only works on numeric data optionally multiplying strings.");
        }
    }

    /**
     * 100 * A / B
     *  
     * @param metaA
     * @param dataA
     * @param metaB
     * @param dataB
     * @return
     * @throws KettleValueException
     */
    public static Object percent1(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {
        if (dataA == null || dataB == null)
            return null;

        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(100.0 * metaA.getNumber(dataA).doubleValue() / metaB.getNumber(dataB).doubleValue());
        case ValueMetaInterface.TYPE_INTEGER:
            return new Long(100 * metaA.getInteger(dataA).longValue() / metaB.getInteger(dataB).longValue());
        case ValueMetaInterface.TYPE_BIGNUMBER:
            return metaA.getBigNumber(dataA).multiply(new BigDecimal(100)).divide(metaB.getBigNumber(dataB),
                    BigDecimal.ROUND_HALF_UP);

        default:
            throw new KettleValueException("The 'percent1' function only works on numeric data");
        }
    }

    /**
     * A - ( A * B / 100 )
     *  
     * @param metaA
     * @param dataA
     * @param metaB
     * @param dataB
     * @return
     * @throws KettleValueException
     */
    public static Object percent2(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {
        if (dataA == null || dataB == null)
            return null;

        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(metaA.getNumber(dataA).doubleValue()
                    - (100.0 * metaA.getNumber(dataA).doubleValue() / metaB.getNumber(dataB).doubleValue()));
        case ValueMetaInterface.TYPE_INTEGER:
            return new Long(metaA.getInteger(dataA).longValue()
                    - (100 * metaA.getInteger(dataA).longValue() / metaB.getInteger(dataB).longValue()));
        case ValueMetaInterface.TYPE_BIGNUMBER:
            BigDecimal percentTotal = metaA.getBigNumber(dataA).multiply(new BigDecimal(100))
                    .divide(metaB.getBigNumber(dataB), BigDecimal.ROUND_HALF_UP);
            return metaA.getBigNumber(dataA).subtract(percentTotal);

        default:
            throw new KettleValueException("The 'percent2' function only works on numeric data");
        }
    }

    /**
     * A + ( A * B / 100 )
     *  
     * @param metaA
     * @param dataA
     * @param metaB
     * @param dataB
     * @return
     * @throws KettleValueException
     */
    public static Object percent3(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {
        if (dataA == null || dataB == null)
            return null;

        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(metaA.getNumber(dataA).doubleValue()
                    + (100.0 * metaA.getNumber(dataA).doubleValue() / metaB.getNumber(dataB).doubleValue()));
        case ValueMetaInterface.TYPE_INTEGER:
            return new Long(metaA.getInteger(dataA).longValue()
                    + (100 * metaA.getInteger(dataA).longValue() / metaB.getInteger(dataB).longValue()));
        case ValueMetaInterface.TYPE_BIGNUMBER:
            BigDecimal percentTotal = metaA.getBigNumber(dataA).multiply(new BigDecimal(100))
                    .divide(metaB.getBigNumber(dataB), BigDecimal.ROUND_HALF_UP);
            return metaA.getBigNumber(dataA).add(percentTotal);

        default:
            throw new KettleValueException("The 'percent3' function only works on numeric data");
        }
    }

    /**
     * A + B * C
     *  
     * @param metaA
     * @param dataA
     * @param metaB
     * @param dataB
     * @return
     * @throws KettleValueException
     */
    public static Object combination1(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB,
            Object dataB, ValueMetaInterface metaC, Object dataC) throws KettleValueException {
        if (dataA == null || dataB == null || dataC == null)
            return null;

        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(metaA.getNumber(dataA).doubleValue()
                    + (metaB.getNumber(dataB).doubleValue() * metaC.getNumber(dataC).doubleValue()));
        case ValueMetaInterface.TYPE_INTEGER:
            return new Long(metaA.getInteger(dataA).longValue()
                    + (metaB.getInteger(dataB).longValue() * metaC.getInteger(dataC).longValue()));
        case ValueMetaInterface.TYPE_BIGNUMBER:
            BigDecimal product = metaB.getBigNumber(dataB).multiply(metaC.getBigNumber(dataC));
            return metaA.getBigNumber(dataA).add(product);

        default:
            throw new KettleValueException("The 'combination1' function only works on numeric data");
        }
    }

    /**
    * SQRT( A*A + B*B )
    *  
    * @param metaA
    * @param dataA
    * @param metaB
    * @param dataB
    * @return
    * @throws KettleValueException
    */
    public static Object combination2(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB,
            Object dataB) throws KettleValueException {
        if (dataA == null || dataB == null)
            return null;

        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(Math.sqrt(metaA.getNumber(dataA).doubleValue() * metaA.getNumber(dataA).doubleValue()
                    + metaB.getNumber(dataB).doubleValue() * metaB.getNumber(dataB).doubleValue()));

        case ValueMetaInterface.TYPE_INTEGER:
            return new Long(
                    Math.round(Math.sqrt(metaA.getInteger(dataA).longValue() * metaA.getInteger(dataA).longValue()
                            + metaB.getInteger(dataB).longValue() / metaB.getInteger(dataB).longValue())));

        case ValueMetaInterface.TYPE_BIGNUMBER:
            return BigDecimal
                    .valueOf(Math.sqrt(metaA.getNumber(dataA).doubleValue() * metaA.getNumber(dataA).doubleValue()
                            + metaB.getNumber(dataB).doubleValue() * metaB.getNumber(dataB).doubleValue()));

        default:
            throw new KettleValueException("The 'combination2' function only works on numeric data");
        }
    }

    public static Object round(ValueMetaInterface metaA, Object dataA) throws KettleValueException {
        if (dataA == null)
            return null;

        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(Math.round(metaA.getNumber(dataA).doubleValue()));
        case ValueMetaInterface.TYPE_INTEGER:
            return metaA.getInteger(dataA);
        case ValueMetaInterface.TYPE_BIGNUMBER:
            return new BigDecimal(Math.round(metaA.getNumber(dataA).doubleValue()));

        default:
            throw new KettleValueException("The 'round' function only works on numeric data");
        }
    }

    public static Object abs(ValueMetaInterface metaA, Object dataA) throws KettleValueException {
        if (dataA == null)
            return null;

        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(Math.abs(metaA.getNumber(dataA).doubleValue()));
        case ValueMetaInterface.TYPE_INTEGER:
            return metaA.getInteger(Math.abs(metaA.getNumber(dataA).longValue()));
        case ValueMetaInterface.TYPE_BIGNUMBER:
            return new BigDecimal(Math.abs(metaA.getNumber(dataA).doubleValue()));

        default:
            throw new KettleValueException("The 'abs' function only works on numeric data");
        }
    }

    public static Object round(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {
        if (dataA == null || dataB == null)
            return null;

        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(
                    Const.round(metaA.getNumber(dataA).doubleValue(), metaB.getInteger(dataB).intValue()));
        case ValueMetaInterface.TYPE_INTEGER:
            return metaA.getInteger(dataA);
        case ValueMetaInterface.TYPE_BIGNUMBER:

            // Round it to the desired number of digits.
            BigDecimal number = metaA.getBigNumber(dataA);
            return number.setScale(metaB.getInteger(dataB).intValue(), BigDecimal.ROUND_HALF_EVEN);

        default:
            throw new KettleValueException("The 'round' function only works on numeric data");
        }
    }

    public static Object nvl(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {
        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_STRING:
            if (dataA == null)
                return metaB.getString(dataB);
            else
                return metaA.getString(dataA);

        case ValueMetaInterface.TYPE_NUMBER:
            if (dataA == null)
                return metaB.getNumber(dataB);
            else
                return metaA.getNumber(dataA);

        case ValueMetaInterface.TYPE_INTEGER:
            if (dataA == null)
                return metaB.getInteger(dataB);
            else
                return metaA.getInteger(dataA);

        case ValueMetaInterface.TYPE_BIGNUMBER:
            if (dataA == null)
                return metaB.getBigNumber(dataB);
            else
                return metaA.getBigNumber(dataA);

        case ValueMetaInterface.TYPE_DATE:
            if (dataA == null)
                return metaB.getDate(dataB);
            else
                return metaA.getDate(dataA);

        case ValueMetaInterface.TYPE_BOOLEAN:
            if (dataA == null)
                return metaB.getBoolean(dataB);
            else
                return metaA.getBoolean(dataA);

        case ValueMetaInterface.TYPE_BINARY:
            if (dataA == null)
                return metaB.getBinary(dataB);
            else
                return metaA.getBinary(dataA);

        default:
            throw new KettleValueException(
                    "The 'nvl' function doesn't know how to handle data type " + metaA.getType());
        }
    }

    public static Object removeTimeFromDate(ValueMetaInterface metaA, Object dataA) throws KettleValueException {
        if (metaA.isDate()) {
            Calendar cal = Calendar.getInstance();
            Date date = metaA.getDate(dataA);
            if (date != null) {
                cal.setTime(date);
                return Const.removeTimeFromDate(date);
            } else
                return null;
        }

        throw new KettleValueException("The 'removeTimeFromDate' function only works with a date");
    }

    public static Object addTimeToDate(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB,
            Object dataB, ValueMetaInterface metaC, Object dataC) throws KettleValueException {
        if (dataA == null)
            return null;
        if (metaA.isDate()) {
            try {
                if (dataC == null)
                    return Const.addTimeToDate(metaA.getDate(dataA), metaB.getString(dataB), null);
                else
                    return Const.addTimeToDate(metaA.getDate(dataA), metaB.getString(dataB),
                            metaC.getString(dataC));
            } catch (Exception e) {
                throw new KettleValueException(e);
            }
        }

        throw new KettleValueException("The 'addTimeToDate' function only works with a date");
    }

    public static Object addDays(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {
        if (metaA.isDate() && metaB.isInteger()) {
            Calendar cal = Calendar.getInstance();
            cal.setTime(metaA.getDate(dataA));
            cal.add(Calendar.DAY_OF_YEAR, metaB.getInteger(dataB).intValue());

            return cal.getTime();
        }

        throw new KettleValueException("The 'addDays' function only works with a date and an integer");
    }

    public static Object DateDiff(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {
        if (metaA.isDate() && metaB.isDate()) {
            if (dataA != null && dataB != null) {
                // Get msec from each, and subtract.
                long diff = metaA.getDate(dataA).getTime() - metaB.getDate(dataB).getTime();
                return new Long(diff / (1000 * 60 * 60 * 24));
            } else
                return null;
        }

        throw new KettleValueException("The 'DateDiff' function only works with dates");
    }

    public static Object yearOfDate(ValueMetaInterface metaA, Object dataA) throws KettleValueException {
        if (dataA == null)
            return null;

        if (metaA.isDate()) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(metaA.getDate(dataA));
            return new Long(calendar.get(Calendar.YEAR));
        }

        throw new KettleValueException("The 'yearOfDate' function only works with dates");
    }

    public static Object monthOfDate(ValueMetaInterface metaA, Object dataA) throws KettleValueException {
        if (dataA == null)
            return null;

        if (metaA.isDate()) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(metaA.getDate(dataA));
            return new Long(calendar.get(Calendar.MONTH) + 1);
        }

        throw new KettleValueException("The 'monthOfDate' function only works with dates");
    }

    public static Object dayOfYear(ValueMetaInterface metaA, Object dataA) throws KettleValueException {
        if (dataA == null)
            return null;

        if (metaA.isDate()) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(metaA.getDate(dataA));
            return new Long(calendar.get(Calendar.DAY_OF_YEAR));
        }

        throw new KettleValueException("The 'dayOfYear' function only works with dates");
    }

    public static Object dayOfMonth(ValueMetaInterface metaA, Object dataA) throws KettleValueException {
        if (dataA == null)
            return null;

        if (metaA.isDate()) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(metaA.getDate(dataA));
            return new Long(calendar.get(Calendar.DAY_OF_MONTH));
        }

        throw new KettleValueException("The 'dayOfMonth' function only works with dates");
    }

    public static Object dayOfWeek(ValueMetaInterface metaA, Object dataA) throws KettleValueException {
        if (dataA == null)
            return null;

        if (metaA.isDate()) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(metaA.getDate(dataA));
            return new Long(calendar.get(Calendar.DAY_OF_WEEK));
        }

        throw new KettleValueException("The 'dayOfWeek' function only works with dates");
    }

    public static Object weekOfYear(ValueMetaInterface metaA, Object dataA) throws KettleValueException {
        if (dataA == null)
            return null;

        if (metaA.isDate()) {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(metaA.getDate(dataA));
            return new Long(calendar.get(Calendar.WEEK_OF_YEAR));
        }

        throw new KettleValueException("The 'weekOfYear' function only works with dates");
    }

    public static Object weekOfYearISO8601(ValueMetaInterface metaA, Object dataA) throws KettleValueException {
        if (dataA == null)
            return null;

        if (metaA.isDate()) {
            Calendar calendar = Calendar.getInstance(Locale.ENGLISH);
            calendar.setMinimalDaysInFirstWeek(4);
            calendar.setFirstDayOfWeek(Calendar.MONDAY);
            calendar.setTime(metaA.getDate(dataA));
            return new Long(calendar.get(Calendar.WEEK_OF_YEAR));
        }

        throw new KettleValueException("The 'weekOfYearISO8601' function only works with dates");
    }

    public static Object yearOfDateISO8601(ValueMetaInterface metaA, Object dataA) throws KettleValueException {
        if (dataA == null)
            return null;

        if (metaA.isDate()) {
            Calendar calendar = Calendar.getInstance(Locale.ENGLISH);
            calendar.setMinimalDaysInFirstWeek(4);
            calendar.setFirstDayOfWeek(Calendar.MONDAY);
            calendar.setTime(metaA.getDate(dataA));

            int week = calendar.get(Calendar.WEEK_OF_YEAR);
            int month = calendar.get(Calendar.MONTH);
            int year = calendar.get(Calendar.YEAR);

            // fix up for the year taking into account ISO8601 weeks
            if (week >= 52 && month == 0)
                year--;
            if (week <= 2 && month == 11)
                year++;

            return new Long(year);
        }

        throw new KettleValueException("The 'yearOfDateISO8601' function only works with dates");
    }

    /**
     * Change a hexadecimal string into normal ASCII representation. E.g. if Value
     * contains string "61" afterwards it would contain value "a". If the
     * hexadecimal string is of odd length a leading zero will be used.
     *
     * Note that only the low byte of a character will be processed, this
     * is for binary transformations.
     *
     * @return Value itself
     * @throws KettleValueException  
     */
    public static String hexToByteDecode(ValueMetaInterface meta, Object data) throws KettleValueException {
        if (meta.isNull(data)) {
            return null;
        }

        String hexString = meta.getString(data);

        int len = hexString.length();
        char chArray[] = new char[(len + 1) / 2];
        boolean evenByte = true;
        int nextByte = 0;

        // we assume a leading 0 if the length is not even.
        if ((len % 2) == 1)
            evenByte = false;

        int nibble;
        int i, j;
        for (i = 0, j = 0; i < len; i++) {
            char c = hexString.charAt(i);

            if ((c >= '0') && (c <= '9'))
                nibble = c - '0';
            else if ((c >= 'A') && (c <= 'F'))
                nibble = c - 'A' + 0x0A;
            else if ((c >= 'a') && (c <= 'f'))
                nibble = c - 'a' + 0x0A;
            else
                throw new KettleValueException("invalid hex digit '" + c + "'.");

            if (evenByte) {
                nextByte = (nibble << 4);
            } else {
                nextByte += nibble;
                chArray[j] = (char) nextByte;
                j++;
            }

            evenByte = !evenByte;
        }
        return new String(chArray);
    }

    /**
     * Change a string into its hexadecimal representation. E.g. if Value
     * contains string "a" afterwards it would contain value "0061".
     * 
     * Note that transformations happen in groups of 4 hex characters, so
     * the value of a characters is always in the range 0-65535.
     *  
     * @return 
     * @throws KettleValueException
     */
    public static String byteToHexEncode(ValueMetaInterface metaA, Object dataA) throws KettleValueException {
        if (dataA == null)
            return null;

        final char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

        String hex = metaA.getString(dataA);

        char[] s = hex.toCharArray();
        StringBuffer hexString = new StringBuffer(2 * s.length);

        for (int i = 0; i < s.length; i++) {
            hexString.append(hexDigits[(s[i] & 0x00F0) >> 4]); // hi nibble
            hexString.append(hexDigits[s[i] & 0x000F]); // lo nibble
        }

        return hexString.toString();
    }

    /**
     * Change a string into its hexadecimal representation. E.g. if Value
     * contains string "a" afterwards it would contain value "0061".
     * 
     * Note that transformations happen in groups of 4 hex characters, so
     * the value of a characters is always in the range 0-65535.
     *  
     * @return A string with Hex code
     * @throws KettleValueException In case of a data conversion problem.
     */
    public static String charToHexEncode(ValueMetaInterface meta, Object data) throws KettleValueException {
        final char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

        if (meta.isNull(data)) {
            return null;
        }

        String hex = meta.getString(data);

        char[] s = hex.toCharArray();
        StringBuffer hexString = new StringBuffer(2 * s.length);

        for (int i = 0; i < s.length; i++) {
            hexString.append(hexDigits[(s[i] & 0xF000) >> 12]); // hex 1
            hexString.append(hexDigits[(s[i] & 0x0F00) >> 8]); // hex 2
            hexString.append(hexDigits[(s[i] & 0x00F0) >> 4]); // hex 3
            hexString.append(hexDigits[s[i] & 0x000F]); // hex 4
        }

        return hexString.toString();
    }

    /**
     * Change a hexadecimal string into normal ASCII representation. E.g. if Value
     * contains string "61" afterwards it would contain value "a". If the
     * hexadecimal string is of a wrong length leading zeroes will be used.
     *
     * Note that transformations happen in groups of 4 hex characters, so
     * the value of a characters is always in the range 0-65535.
     *
     * @return A hex-to-char decoded String 
     * @throws KettleValueException  
     */
    public static String hexToCharDecode(ValueMetaInterface meta, Object data) throws KettleValueException {
        if (meta.isNull(data)) {
            return null;
        }

        String hexString = meta.getString(data);

        int len = hexString.length();
        char chArray[] = new char[(len + 3) / 4];
        int charNr;
        int nextChar = 0;

        // we assume a leading 0s if the length is not right.
        charNr = (len % 4);
        if (charNr == 0)
            charNr = 4;

        int nibble;
        int i, j;
        for (i = 0, j = 0; i < len; i++) {
            char c = hexString.charAt(i);

            if ((c >= '0') && (c <= '9'))
                nibble = c - '0';
            else if ((c >= 'A') && (c <= 'F'))
                nibble = c - 'A' + 0x0A;
            else if ((c >= 'a') && (c <= 'f'))
                nibble = c - 'a' + 0x0A;
            else
                throw new KettleValueException("invalid hex digit '" + c + "'.");

            if (charNr == 4) {
                nextChar = (nibble << 12);
                charNr--;
            } else if (charNr == 3) {
                nextChar += (nibble << 8);
                charNr--;
            } else if (charNr == 2) {
                nextChar += (nibble << 4);
                charNr--;
            } else // charNr == 1
            {
                nextChar += nibble;
                chArray[j] = (char) nextChar;
                charNr = 4;
                j++;
            }
        }

        return new String(chArray);
    }

    /**
     * Right pad a string: adds spaces to a string until a certain length.
     * If the length is smaller then the limit specified, the String is truncated.
     * @param ret The string to pad
     * @param limit The desired length of the padded string.
     * @return The padded String.
     */
    public static final String rightPad(String ret, int limit) {
        if (ret == null)
            return rightPad(new StringBuffer(), limit);
        else
            return rightPad(new StringBuffer(ret), limit);
    }

    /**
     * Right pad a StringBuffer: adds spaces to a string until a certain length.
     * If the length is smaller then the limit specified, the String is truncated.
     * @param ret The StringBuffer to pad
     * @param limit The desired length of the padded string.
     * @return The padded String.
     */
    public static final String rightPad(StringBuffer ret, int limit) {
        int len = ret.length();
        int l;

        if (len > limit) {
            ret.setLength(limit);
        } else {
            for (l = len; l < limit; l++)
                ret.append(' ');
        }
        return ret.toString();
    }

    /**
     * Replace value occurances in a String with another value.
     * @param string The original String.
     * @param repl The text to replace
     * @param with The new text bit
     * @return The resulting string with the text pieces replaced.
     */
    public static final String replace(String string, String repl, String with) {
        StringBuffer str = new StringBuffer(string);
        for (int i = str.length() - 1; i >= 0; i--) {
            if (str.substring(i).startsWith(repl)) {
                str.delete(i, i + repl.length());
                str.insert(i, with);
            }
        }
        return str.toString();
    }

    /**
     * Alternate faster version of string replace using a stringbuffer as input.
     * 
     * @param str The string where we want to replace in
     * @param code The code to search for
     * @param repl The replacement string for code
     */
    public static void replaceBuffer(StringBuffer str, String code, String repl) {
        int clength = code.length();

        int i = str.length() - clength;

        while (i >= 0) {
            String look = str.substring(i, i + clength);
            if (look.equalsIgnoreCase(code)) // Look for a match!
            {
                str.replace(i, i + clength, repl);
            }
            i--;
        }
    }

    /**
     * Count the number of spaces to the left of a text. (leading)
     * @param field The text to examine
     * @return The number of leading spaces found.
     */
    public static final int nrSpacesBefore(String field) {
        int nr = 0;
        int len = field.length();
        while (nr < len && field.charAt(nr) == ' ') {
            nr++;
        }
        return nr;
    }

    /**
     * Count the number of spaces to the right of a text. (trailing)
     * @param field The text to examine
     * @return The number of trailing spaces found.
     */
    public static final int nrSpacesAfter(String field) {
        int nr = 0;
        int len = field.length();
        while (nr < len && field.charAt(field.length() - 1 - nr) == ' ') {
            nr++;
        }
        return nr;
    }

    /**
     * Checks whether or not a String consists only of spaces.
     * @param str The string to check
     * @return true if the string has nothing but spaces.
     */
    public static final boolean onlySpaces(String str) {
        for (int i = 0; i < str.length(); i++)
            if (!isSpace(str.charAt(i)))
                return false;
        return true;
    }
}