org.pentaho.di.core.row.ValueDataUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.di.core.row.ValueDataUtil.java

Source

/*! ******************************************************************************
 *
 * Pentaho Data Integration
 *
 * Copyright (C) 2002-2013 by Pentaho : http://www.pentaho.com
 *
 *******************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ******************************************************************************/

package org.pentaho.di.core.row;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.math.BigDecimal;
import java.math.MathContext;
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.codec.language.RefinedSoundex;
import org.apache.commons.codec.language.Soundex;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.provider.local.LocalFile;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.exception.KettleValueException;
import org.pentaho.di.core.fileinput.CharsetToolkit;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.vfs.KettleVFS;
import org.pentaho.di.core.xml.XMLCheck;

import com.wcohen.ss.Jaro;
import com.wcohen.ss.JaroWinkler;
import com.wcohen.ss.NeedlemanWunsch;

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

    /**
     * @deprecated Use {@link Const#rtrim(String)} instead
     */
    @Deprecated
    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
     */
    @Deprecated
    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
     */
    @Deprecated
    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()));
    }

    /**
     * DamerauLevenshtein distance 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 getDamerauLevenshtein_Distance(ValueMetaInterface metaA, Object dataA,
            ValueMetaInterface metaB, Object dataB) {
        if (dataA == null || dataB == null) {
            return null;
        }
        return new Long(Utils.getDamerauLevenshteinDistance(dataA.toString(), dataB.toString()));
    }

    /**
     * NeedlemanWunsch distance 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 getNeedlemanWunsch_Distance(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB,
            Object dataB) {
        if (dataA == null || dataB == null) {
            return null;
        }
        return new Long((int) new NeedlemanWunsch().score(dataA.toString(), dataB.toString()));
    }

    /**
     * Jaro similitude is a measure of the similarity between two strings, which we will refer to as the source string (s)
     * and the target string (t).
     */
    public static Double getJaro_Similitude(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB,
            Object dataB) {
        if (dataA == null || dataB == null) {
            return null;
        }
        return new Double(new Jaro().score(dataA.toString(), dataB.toString()));
    }

    /**
     * JaroWinkler similitude is a measure of the similarity between two strings, which we will refer to as the source
     * string (s) and the target string (t).
     */
    public static Double getJaroWinkler_Similitude(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB,
            Object dataB) {
        if (dataA == null || dataB == null) {
            return null;
        }
        return new Double(new JaroWinkler().score(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 get_SoundEx(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null) {
            return null;
        }
        return (new Soundex()).encode(dataA.toString());
    }

    public static String get_RefinedSoundEx(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null) {
            return null;
        }
        return (new RefinedSoundex()).encode(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 escapeXML(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null) {
            return null;
        }
        return Const.escapeXML(dataA.toString());
    }

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

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

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

    public static String escapeSQL(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null) {
            return null;
        }
        return Const.escapeSQL(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) {
            // ignore - should likely log the exception
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e) {
                // Ignore
            }
        }

        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(((LocalFile) file).getInputStream(), new CRC32());
            byte[] buf = new byte[128];
            int readSize = 0;
            do {
                readSize = cis.read(buf);
            } while (readSize >= 0);

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

        } catch (Exception e) {
            // ignore - should likely log the exception
        } finally {
            if (file != null) {
                try {
                    file.close();
                    file = null;
                } catch (Exception e) {
                    // Ignore
                }
            }
        }
        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(((LocalFile) file).getInputStream(), new Adler32());

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

        } catch (Exception e) {
            // throw new Exception(e);
        } finally {
            if (file != null) {
                try {
                    file.close();
                    file = null;
                } catch (Exception e) {
                    // Ignore
                }
            }
        }
        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 (fis != null) {
                    fis.close();
                }
                fis = null;
                if (file != null) {
                    file.close();
                }
                file = null;
            } catch (Exception e) {
                // Ignore
            }
        }
    }

    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));
        default:
            return new Long(metaA.getInteger(dataA).longValue() - metaB.getInteger(dataB).longValue());
        }
    }

    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())) {
            return multiplyString(metaA, dataA, metaB, dataB);
        }

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

    protected static Object multiplyNumeric(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB,
            Object dataB) throws KettleValueException {
        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_NUMBER:
            return multiplyDoubles(metaA.getNumber(dataA), metaB.getNumber(dataB));
        case ValueMetaInterface.TYPE_INTEGER:
            return multiplyLongs(metaA.getInteger(dataA), metaB.getInteger(dataB));
        case ValueMetaInterface.TYPE_BIGNUMBER:
            return multiplyBigDecimals(metaA.getBigNumber(dataA), metaB.getBigNumber(dataB), null);

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

    public static Double multiplyDoubles(Double a, Double b) {
        return new Double(a.doubleValue() * b.doubleValue());
    }

    public static Long multiplyLongs(Long a, Long b) {
        return new Long(a.longValue() * b.longValue());
    }

    public static BigDecimal multiplyBigDecimals(BigDecimal a, BigDecimal b, MathContext mc) {
        if (mc == null) {
            mc = MathContext.DECIMAL64;
        }
        return a.multiply(b, mc);
    }

    protected static Object multiplyString(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB,
            Object dataB) throws KettleValueException {
        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();
    }

    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 divideDoubles(metaA.getNumber(dataA), metaB.getNumber(dataB));
        case ValueMetaInterface.TYPE_INTEGER:
            return divideLongs(metaA.getInteger(dataA), metaB.getInteger(dataB));
        case ValueMetaInterface.TYPE_BIGNUMBER:
            return divideBigDecimals(metaA.getBigNumber(dataA), metaB.getBigNumber(dataB), null);

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

    public static Double divideDoubles(Double a, Double b) {
        return new Double(a.doubleValue() / b.doubleValue());
    }

    public static Long divideLongs(Long a, Long b) {
        return new Long(a.longValue() / b.longValue());
    }

    public static BigDecimal divideBigDecimals(BigDecimal a, BigDecimal b, MathContext mc) {
        if (mc == null) {
            mc = MathContext.DECIMAL64;
        }
        return a.divide(b, mc);
    }

    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 'sqrt' function only works on numeric data.");
        }
    }

    /**
     * 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 divideDoubles(multiplyDoubles(100.0D, metaA.getNumber(dataA)), metaB.getNumber(dataB));
        case ValueMetaInterface.TYPE_INTEGER:
            return divideLongs(multiplyLongs(100L, metaA.getInteger(dataA)), metaB.getInteger(dataB));
        case ValueMetaInterface.TYPE_BIGNUMBER:
            return divideBigDecimals(multiplyBigDecimals(metaA.getBigNumber(dataA), new BigDecimal(100), null),
                    metaB.getBigNumber(dataB), null);

        default:
            throw new KettleValueException("The 'A/B in %' 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()
                    - divideDoubles(multiplyDoubles(metaA.getNumber(dataA), metaB.getNumber(dataB)), 100.0D));
        case ValueMetaInterface.TYPE_INTEGER:
            return new Long(metaA.getInteger(dataA).longValue()
                    - divideLongs(multiplyLongs(metaA.getInteger(dataA), metaB.getInteger(dataB)), 100L));
        case ValueMetaInterface.TYPE_BIGNUMBER:
            return metaA.getBigNumber(dataA).subtract(divideBigDecimals(metaA.getBigNumber(dataA),
                    multiplyBigDecimals(metaB.getBigNumber(dataB), new BigDecimal(100), null), null));
        default:
            throw new KettleValueException("The 'A-B%' 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()
                    + divideDoubles(multiplyDoubles(metaA.getNumber(dataA), metaB.getNumber(dataB)), 100.0D));
        case ValueMetaInterface.TYPE_INTEGER:
            return new Long(metaA.getInteger(dataA).longValue()
                    + divideLongs(multiplyLongs(metaA.getInteger(dataA), metaB.getInteger(dataB)), 100L));
        case ValueMetaInterface.TYPE_BIGNUMBER:
            return metaA.getBigNumber(dataA).add(divideBigDecimals(metaA.getBigNumber(dataA),
                    multiplyBigDecimals(metaB.getBigNumber(dataB), new BigDecimal(100), null), null));
        default:
            throw new KettleValueException("The 'A+B%' 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:
            return metaA.getBigNumber(dataA)
                    .add(multiplyBigDecimals(metaB.getBigNumber(dataB), metaC.getBigNumber(dataC), null));

        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");
        }
    }

    /**
     * Rounding with no decimal places (using default rounding method ROUND_HALF_EVEN)
     *
     * @param metaA
     *          Metadata of value to round
     * @param dataA
     *          Value to round
     * @return The rounded value
     * @throws KettleValueException
     */
    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");
        }
    }

    /**
     * Rounding with no decimal places with a given rounding method
     *
     * @param metaA
     *          Metadata of value to round
     * @param dataA
     *          Value to round
     * @param roundingMode
     *          The mode for rounding, e.g. java.math.BigDecimal.ROUND_HALF_EVEN
     * @return The rounded value
     * @throws KettleValueException
     */
    public static Object round(ValueMetaInterface metaA, Object dataA, int roundingMode)
            throws KettleValueException {
        if (dataA == null) {
            return null;
        }

        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(Const.round(metaA.getNumber(dataA).doubleValue(), 0, roundingMode));
        case ValueMetaInterface.TYPE_INTEGER:
            return metaA.getInteger(dataA);
        case ValueMetaInterface.TYPE_BIGNUMBER:
            // Round it to 0 digits.
            BigDecimal number = metaA.getBigNumber(dataA);
            return number.setScale(0, roundingMode);
        default:
            throw new KettleValueException("The 'round' function only works on numeric data");
        }
    }

    /**
     * Rounding with decimal places (using default rounding method ROUND_HALF_EVEN)
     *
     * @param metaA
     *          Metadata of value to round
     * @param dataA
     *          Value to round
     * @param metaB
     *          Metadata of decimal places
     * @param dataB
     *          decimal places
     * @return The rounded value
     * @throws KettleValueException
     */
    public static Object round(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {
        return round(metaA, dataA, metaB, dataB, BigDecimal.ROUND_HALF_EVEN);
    }

    /**
     * Rounding with decimal places with a given rounding method
     *
     * @param metaA
     *          Metadata of value to round
     * @param dataA
     *          Value to round
     * @param metaB
     *          Metadata of decimal places
     * @param dataB
     *          decimal places
     * @param roundingMode
     *          roundingMode The mode for rounding, e.g. java.math.BigDecimal.ROUND_HALF_EVEN
     * @return The rounded value
     * @throws KettleValueException
     */
    public static Object round(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB,
            int roundingMode) 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(),
                    roundingMode));
        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(), roundingMode);

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

    public static Object ceil(ValueMetaInterface metaA, Object dataA) throws KettleValueException {
        if (dataA == null) {
            return null;
        }
        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(Math.ceil(metaA.getNumber(dataA).doubleValue()));
        case ValueMetaInterface.TYPE_INTEGER:
            return metaA.getInteger(dataA);
        case ValueMetaInterface.TYPE_BIGNUMBER:
            return new BigDecimal(Math.ceil(metaA.getNumber(dataA).doubleValue()));

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

    public static Object floor(ValueMetaInterface metaA, Object dataA) throws KettleValueException {
        if (dataA == null) {
            return null;
        }
        switch (metaA.getType()) {
        case ValueMetaInterface.TYPE_NUMBER:
            return new Double(Math.floor(metaA.getNumber(dataA).doubleValue()));
        case ValueMetaInterface.TYPE_INTEGER:
            return metaA.getInteger(dataA);
        case ValueMetaInterface.TYPE_BIGNUMBER:
            return new BigDecimal(Math.floor(metaA.getNumber(dataA).doubleValue()));

        default:
            throw new KettleValueException("The 'floor' 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 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:
            if (dataA == null) {
                return metaB.getNativeDataType(dataB);
            } else {
                return metaA.getNativeDataType(dataA);
            }
        }
    }

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

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

        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);
        }
    }

    public static Object addDays(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {

        Calendar cal = Calendar.getInstance();
        cal.setTime(metaA.getDate(dataA));
        cal.add(Calendar.DAY_OF_YEAR, metaB.getInteger(dataB).intValue());

        return cal.getTime();
    }

    public static Object addHours(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {

        Calendar cal = Calendar.getInstance();
        cal.setTime(metaA.getDate(dataA));
        cal.add(Calendar.HOUR_OF_DAY, metaB.getInteger(dataB).intValue());

        return cal.getTime();
    }

    public static Object addMinutes(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {

        Calendar cal = Calendar.getInstance();
        cal.setTime(metaA.getDate(dataA));
        cal.add(Calendar.MINUTE, metaB.getInteger(dataB).intValue());

        return cal.getTime();
    }

    public static Object addMonths(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB)
            throws KettleValueException {

        if (dataA != null && dataB != null) {
            Calendar cal = Calendar.getInstance();
            cal.setTime(metaA.getDate(dataA));
            int year = cal.get(Calendar.YEAR);
            int month = cal.get(Calendar.MONTH);
            int day = cal.get(Calendar.DAY_OF_MONTH);

            month += metaB.getInteger(dataB).intValue();

            int newyear = year + (int) Math.floor(month / 12);
            int newmonth = month % 12;

            cal.set(newyear, newmonth, 1);
            int newday = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
            if (newday < day) {
                cal.set(Calendar.DAY_OF_MONTH, newday);
            } else {
                cal.set(Calendar.DAY_OF_MONTH, day);
            }

            return (cal.getTime());
        } else {
            throw new KettleValueException("Unable to add months with a null value");
        }

    }

    /**
     * Returns the number of days that have elapsed between dataA and dataB.
     *
     * @param metaA
     * @param dataA
     *          The "end date"
     * @param metaB
     * @param dataB
     *          The "start date"
     * @param resultType
     *          The "result type" (ms, s, mn, h, d)
     * @return Number of days
     * @throws KettleValueException
     */

    public static Object DateDiff(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB,
            String resultType) throws KettleValueException {

        if (dataA != null && dataB != null) {
            Date startDate = metaB.getDate(dataB);
            Date endDate = metaA.getDate(dataA);

            Calendar stDateCal = Calendar.getInstance();
            Calendar endDateCal = Calendar.getInstance();
            stDateCal.setTime(startDate);
            endDateCal.setTime(endDate);

            long endL = endDateCal.getTimeInMillis()
                    + endDateCal.getTimeZone().getOffset(endDateCal.getTimeInMillis());
            long startL = stDateCal.getTimeInMillis()
                    + stDateCal.getTimeZone().getOffset(stDateCal.getTimeInMillis());
            long diff = endL - startL;

            if (Const.isEmpty(resultType)) {
                return new Long(diff / 86400000);
            } else if (resultType.equals("ms")) {
                return new Long(diff);
            } else if (resultType.equals("s")) {
                return new Long(diff / 1000); // second
            } else if (resultType.equals("mn")) {
                return new Long(diff / 60000); // minute
            } else if (resultType.equals("h")) {
                return new Long(diff / 3600000); // hour
            } else if (resultType.equals("d")) {
                return new Long(diff / 86400000);
            } else {
                throw new KettleValueException("Unknown result type option '" + resultType + "'");
            }
        } else {
            return null;
        }
    }

    public static Object DateWorkingDiff(ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB,
            Object dataB) throws KettleValueException {
        if (dataA != null && dataB != null) {
            Date fromDate = metaB.getDate(dataB);
            Date toDate = metaA.getDate(dataA);
            boolean singminus = false;

            if (fromDate.after(toDate)) {
                singminus = true;
                Date temp = fromDate;
                fromDate = toDate;
                toDate = temp;
            }
            Calendar calFrom = Calendar.getInstance();
            calFrom.setTime(fromDate);
            Calendar calTo = Calendar.getInstance();
            calTo.setTime(toDate);
            int iNoOfWorkingDays = 0;
            do {
                if (calFrom.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY
                        && calFrom.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY) {
                    iNoOfWorkingDays += 1;
                }
                calFrom.add(Calendar.DATE, 1);
            } while (calFrom.getTimeInMillis() < calTo.getTimeInMillis());
            return new Long(singminus ? -iNoOfWorkingDays : iNoOfWorkingDays);
        } else {
            return null;
        }
    }

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

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

    }

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

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

    }

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

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(metaA.getDate(dataA));
        return new Long((calendar.get(Calendar.MONTH) + 3) / 3);
    }

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

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

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

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

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

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(metaA.getDate(dataA));
        return new Long(calendar.get(Calendar.HOUR_OF_DAY));
    }

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

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(metaA.getDate(dataA));
        return new Long(calendar.get(Calendar.MINUTE));
    }

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

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(metaA.getDate(dataA));
        return new Long(calendar.get(Calendar.SECOND));
    }

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

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

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

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

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

        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));

    }

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

        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);
    }

    /**
     * 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' };

        // depending on the use case, this code might deliver the wrong values due to extra conversion with toCharArray
        // see Checksum step and PDI-5190
        // "Add Checksum step gives incorrect results (MD5, CRC32, ADLER32, SHA-1 are affected)"
        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;
    }

    /**
     * Checks an xml file is well formed.
     *
     * @param metaA
     *          The ValueMetaInterface
     * @param dataA
     *          The value (filename)
     * @return true if the file is well formed.
     */
    public static boolean isXMLFileWellFormed(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null) {
            return false;
        }
        String filename = dataA.toString();
        FileObject file = null;
        try {
            file = KettleVFS.getFileObject(filename);
            return XMLCheck.isXMLFileWellFormed(file);
        } catch (Exception e) {
            // ignore - we'll return false although would be nice to log it.
        } finally {
            if (file != null) {
                try {
                    file.close();
                } catch (Exception e) {
                    // Ignore
                }
            }
        }
        return false;
    }

    /**
     * Checks an xml string is well formed.
     *
     * @param metaA
     *          The ValueMetaInterface
     * @param dataA
     *          The value (filename)
     * @return true if the file is well formed.
     */
    public static boolean isXMLWellFormed(ValueMetaInterface metaA, Object dataA) {
        if (dataA == null) {
            return false;
        }
        try {
            return XMLCheck.isXMLWellFormed(new ByteArrayInputStream(metaA.getBinary(dataA)));
        } catch (Exception e) {
            // ignore - we'll return false below
        }
        return false;
    }

    /**
     * Get file encoding.
     *
     * @param metaA
     *          The ValueMetaInterface
     * @param dataA
     *          The value (filename)
     * @return file encoding.
     */
    public static String getFileEncoding(ValueMetaInterface metaA, Object dataA) throws KettleValueException {
        if (dataA == null) {
            return null;
        }
        try {
            return CharsetToolkit.guessEncodingName(new File(metaA.getString(dataA)));
        } catch (Exception e) {
            throw new KettleValueException(e);
        }
    }

    /**
     *  Default utility method to get exact zero value according to ValueMetaInterface. Using
     *  this utility method saves from ClassCastExceptions later.
     *  
     * @param type
     * @return
     * @throws KettleValueException
     */
    public static Object getZeroForValueMetaType(ValueMetaInterface type) throws KettleValueException {
        if (type == null) {
            throw new KettleValueException("API error. ValueMetaInterface can't be null!");
        }

        switch (type.getType()) {
        case (ValueMetaInterface.TYPE_INTEGER): {
            return new Long(0);
        }
        case (ValueMetaInterface.TYPE_NUMBER): {
            return new Double(0);
        }
        case (ValueMetaInterface.TYPE_BIGNUMBER): {
            return new BigDecimal(0);
        }
        case (ValueMetaInterface.TYPE_STRING): {
            return "";
        }
        default: {
            throw new KettleValueException("get zero function undefined for data type: " + type.getType());
        }
        }
    }
}