/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2016 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.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.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 {
private static final Log log = LogFactory.getLog( ValueDataUtil.class );
/**
* System property sets rounding mode of calculator's function ROUND(A,B)
* <ul>
* <li>
* -DROUND_2_MODE=ROUND_HALF_EVEN - provides backward compatibility.</li>
* <li>
* -DROUND_2_MODE=ROUND_HALF_CEILING or not specified - makes the effect of ROUND(A,B) like ROUND(A).</li>
* <li>
* If incorrect value set - default value used (ROUND_CEILING).</li>
* </ul>
* See (PDI-9920)
*/
private static final String SYS_PROPERTY_ROUND_2_MODE = "ROUND_2_MODE";
/**
* Value of system property ROUND_2_MODE
* Provides correct rounding (PDI-9920)
*/
private static final String SYS_PROPERTY_ROUND_2_MODE_DEFAULT_VALUE = "ROUND_HALF_CEILING";
private static final int ROUND_2_MODE_DEFAULT_VALUE = Const.ROUND_HALF_CEILING;
/**
* Value of system property ROUND_2_MODE
* Provides backward compatibility (PDI-9920)
*/
private static final String SYS_PROPERTY_ROUND_2_MODE_BACKWARD_COMPATIBILITY_VALUE = "ROUND_HALF_EVEN";
private static final int ROUND_2_MODE_BACKWARD_COMPATIBILITY_VALUE = BigDecimal.ROUND_HALF_EVEN;
/**
* Rounding mode of the ROUND function with 2 arguments (value, precision).
* <ul>
* <li>
* {@code org.pentaho.di.core.Const.ROUND_HALF_CEILING} - ditto as ROUND(value).</li>
* <li>{@code java.math.BigDecimal.ROUND_HALF_EVEN} - backward compatibility</li>
* </ul>
*/
private static int ROUND_2_MODE = readRound2Mode();
private static int readRound2Mode() {
int round2Mode = ROUND_2_MODE_DEFAULT_VALUE;
final String rpaValue = System.getProperty( SYS_PROPERTY_ROUND_2_MODE );
if ( Utils.isEmpty( rpaValue ) ) {
round2Mode = ROUND_2_MODE_DEFAULT_VALUE;
log.debug( "System property is omitted: ROUND_2_MODE. Default value used: " + SYS_PROPERTY_ROUND_2_MODE_DEFAULT_VALUE + "." );
} else if ( SYS_PROPERTY_ROUND_2_MODE_DEFAULT_VALUE.equals( rpaValue ) ) {
round2Mode = ROUND_2_MODE_DEFAULT_VALUE;
log.debug( "System property read: ROUND_2_MODE=" + ROUND_2_MODE_DEFAULT_VALUE + " (default value)" );
} else if ( SYS_PROPERTY_ROUND_2_MODE_BACKWARD_COMPATIBILITY_VALUE.equalsIgnoreCase( rpaValue ) ) {
round2Mode = ROUND_2_MODE_BACKWARD_COMPATIBILITY_VALUE;
log.debug( "System property read: ROUND_2_MODE=" + SYS_PROPERTY_ROUND_2_MODE_BACKWARD_COMPATIBILITY_VALUE
+ " (backward compatibility value)" );
} else {
log.warn( "Incorrect value of system property read: ROUND_2_MODE=" + rpaValue + ". Set to " + SYS_PROPERTY_ROUND_2_MODE_DEFAULT_VALUE
+ " instead." );
}
return round2Mode;
}
/**
* @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: {
Double valueA = metaA.getNumber( dataA );
Double valueB = metaB.getNumber( dataB );
if ( valueB == null ) {
return valueA;
} else if ( valueA == null ) {
return valueB;
} else {
return new Double( valueA.doubleValue() + valueB.doubleValue() );
}
}
case ValueMetaInterface.TYPE_INTEGER: {
Long valueA = metaA.getInteger( dataA );
Long valueB = metaB.getInteger( dataB );
if ( valueB == null ) {
return valueA;
} else if ( valueA == null ) {
return valueB;
} else {
return new Long( valueA.longValue() + valueB.longValue() );
}
}
case ValueMetaInterface.TYPE_BOOLEAN: {
Boolean valueA = metaA.getBoolean( dataA );
Boolean valueB = metaB.getBoolean( dataB );
if ( valueB == null ) {
return valueA;
} else if ( valueA == null ) {
return valueB;
} else {
return Boolean.valueOf( valueA.booleanValue() || valueB.booleanValue() );
}
}
case ValueMetaInterface.TYPE_BIGNUMBER: {
BigDecimal valueA = metaA.getBigNumber( dataA );
BigDecimal valueB = metaB.getBigNumber( dataB );
if ( valueB == null ) {
return valueA;
} else if ( valueA == null ) {
return valueB;
} else {
return valueA.add( valueB );
}
}
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 ) {
Object value = metaA.convertData( metaB, dataB );
metaA.setStorageType( ValueMetaInterface.STORAGE_TYPE_NORMAL );
return value;
}
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_CEILING)
*
* @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() ) {
// Use overloaded Const.round(value, precision, mode)
case ValueMetaInterface.TYPE_NUMBER:
return new Double( Const.round( metaA.getNumber( dataA ), 0, roundingMode ) );
case ValueMetaInterface.TYPE_INTEGER:
return new Long( Const.round( metaA.getInteger( dataA ), 0, roundingMode ) );
case ValueMetaInterface.TYPE_BIGNUMBER:
return Const.round( metaA.getBigNumber( dataA ), 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 {
final Object r = round( metaA, dataA, metaB, dataB, ROUND_2_MODE );
return r;
}
/**
* 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 new Long( Const.round( metaA.getInteger( dataA ).longValue(), metaB.getInteger( dataB ).intValue(),
roundingMode ) );
case ValueMetaInterface.TYPE_BIGNUMBER:
return Const.round( metaA.getBigNumber( dataA ), metaB.getInteger( dataB ).intValue(), roundingMode );
default:
throw new KettleValueException( "The 'round' function only works on numeric data" );
}
}
/**
* 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 metaC
* Metadata of rounding mode
* @param dataC
* rounding mode, 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,
ValueMetaInterface metaC, Object dataC ) throws KettleValueException {
if ( dataA == null || dataB == null || dataC == null ) {
return null;
}
Long valueC = metaC.getInteger( dataC );
if ( valueC == null || valueC < Const.ROUND_HALF_CEILING || valueC > BigDecimal.ROUND_HALF_EVEN ) {
throw new KettleValueException( "The 'round_custom' arg C has incorrect value: " + valueC );
}
int roundingMode = valueC.intValue();
return round( metaA, dataA, metaB, dataB, roundingMode );
}
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" );
}
}
/**
* Returns the remainder (modulus) of A / B.
*
* @param metaA
* @param dataA
* The dividend
* @param metaB
* @param dataB
* The divisor
* @return The remainder
* @throws KettleValueException
*/
public static Object remainder( 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.IEEEremainder( metaA.getNumber( dataA ).doubleValue(), metaB.getNumber( dataB ).doubleValue() ) );
case ValueMetaInterface.TYPE_INTEGER:
return new Long( metaA.getInteger( dataA ) % metaB.getInteger( dataB ) );
case ValueMetaInterface.TYPE_BIGNUMBER:
return metaA.getBigNumber( dataA ).remainder( metaB.getBigNumber( dataB ), MathContext.DECIMAL64 );
default:
throw new KettleValueException( "The 'remainder' 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 addSeconds( ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB ) throws KettleValueException {
Calendar cal = Calendar.getInstance();
cal.setTime( metaA.getDate( dataA ) );
cal.add( Calendar.SECOND, 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 ( Utils.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 ) );
Boolean oldDateCalculation = Boolean.parseBoolean(
Const.getEnvironmentVariable( Const.KETTLE_COMPATIBILITY_CALCULATION_TIMEZONE_DECOMPOSITION, "false" ) );
if ( !oldDateCalculation ) {
calendar.setTimeZone( metaA.getDateFormatTimeZone() );
}
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 ) {
return Const.rightPad( 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 ) {
return Const.rightPad( ret, limit );
}
/**
* 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() );
}
}
}
}