// CHECKSTYLE:FileLength:OFF /*! ****************************************************************************** * * 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.compatibility; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.math.BigDecimal; import java.text.DateFormatSymbols; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; import org.pentaho.di.core.Const; import org.pentaho.di.core.util.Utils; import org.pentaho.di.core.exception.KettleEOFException; import org.pentaho.di.core.exception.KettleFileException; import org.pentaho.di.core.exception.KettleValueException; import org.pentaho.di.core.row.ValueDataUtil; import org.pentaho.di.core.xml.XMLHandler; import org.pentaho.di.core.xml.XMLInterface; import org.w3c.dom.Node; /** * This class is one of the core classes of the Kettle framework. It contains everything you need to manipulate atomic * data (Values/Fields/...) and to describe it in the form of meta-data. (name, length, precision, etc.) * * @author Matt * @since Beginning 2003 */ public class Value implements Cloneable, XMLInterface, Serializable { public static final String XML_TAG = "value"; private static final long serialVersionUID = -6310073485210258622L; /** * Value type indicating that the value has no type set. */ public static final int VALUE_TYPE_NONE = 0; /** * Value type indicating that the value contains a floating point double precision number. */ public static final int VALUE_TYPE_NUMBER = 1; /** * Value type indicating that the value contains a text String. */ public static final int VALUE_TYPE_STRING = 2; /** * Value type indicating that the value contains a Date. */ public static final int VALUE_TYPE_DATE = 3; /** * Value type indicating that the value contains a boolean. */ public static final int VALUE_TYPE_BOOLEAN = 4; /** * Value type indicating that the value contains a long integer. */ public static final int VALUE_TYPE_INTEGER = 5; /** * Value type indicating that the value contains a floating point precision number with arbitrary precision. */ public static final int VALUE_TYPE_BIGNUMBER = 6; /** * Value type indicating that the value contains an Object. */ public static final int VALUE_TYPE_SERIALIZABLE = 7; /** * Value type indicating that the value contains binary data: BLOB, CLOB, ... */ public static final int VALUE_TYPE_BINARY = 8; /** * The descriptions of the value types. */ private static final String[] valueTypeCode = { "-", "Number", "String", "Date", "Boolean", "Integer", "BigNumber", "Serializable", "Binary" }; private ValueInterface value; private String name; private String origin; private boolean NULL; /** * Constructs a new Value of type EMPTY * */ public Value() { // clearValue(); } /** * Constructs a new Value with a name. * * @param name * Sets the name of the Value */ public Value( String name ) { // clearValue(); setName( name ); } /** * Constructs a new Value with a name and a type. * * @param name * Sets the name of the Value * @param val_type * Sets the type of the Value (Value.VALUE_TYPE_*) */ public Value( String name, int val_type ) { // clearValue(); newValue( val_type ); setName( name ); } /** * This method allocates a new value of the appropriate type.. * * @param val_type * The new type of value */ private void newValue( int val_type ) { switch ( val_type ) { case VALUE_TYPE_INTEGER: value = new ValueInteger(); break; case VALUE_TYPE_STRING: value = new ValueString(); break; case VALUE_TYPE_DATE: value = new ValueDate(); break; case VALUE_TYPE_NUMBER: value = new ValueNumber(); break; case VALUE_TYPE_BOOLEAN: value = new ValueBoolean(); break; case VALUE_TYPE_BIGNUMBER: value = new ValueBigNumber(); break; case VALUE_TYPE_BINARY: value = new ValueBinary(); break; default: value = null; } } /** * Convert the value to another type. This only works if a value has been set previously. That is the reason this * method is private. Rather, use the public method setType(int type). * * @param valType * The type to convert to. */ private void convertTo( int valType ) { if ( value != null ) { switch ( valType ) { case VALUE_TYPE_NUMBER: value = new ValueNumber( value.getNumber() ); break; case VALUE_TYPE_STRING: value = new ValueString( value.getString() ); break; case VALUE_TYPE_DATE: value = new ValueDate( value.getDate() ); break; case VALUE_TYPE_BOOLEAN: value = new ValueBoolean( value.getBoolean() ); break; case VALUE_TYPE_INTEGER: value = new ValueInteger( value.getInteger() ); break; case VALUE_TYPE_BIGNUMBER: value = new ValueBigNumber( value.getBigNumber() ); break; case VALUE_TYPE_BINARY: value = new ValueBinary( value.getBytes() ); break; default: value = null; } } } /** * Constructs a new Value with a name, a type, length and precision. * * @param name * Sets the name of the Value * @param valType * Sets the type of the Value (Value.VALUE_TYPE_*) * @param length * The length of the value * @param precision * The precision of the value */ public Value( String name, int valType, int length, int precision ) { this( name, valType ); setLength( length, precision ); } /** * Constructs a new Value of Type VALUE_TYPE_BIGNUMBER, with a name, containing a BigDecimal number * * @param name * Sets the name of the Value * @param bignum * The number to store in this Value */ public Value( String name, BigDecimal bignum ) { // clearValue(); setValue( bignum ); setName( name ); } /** * Constructs a new Value of Type VALUE_TYPE_NUMBER, with a name, containing a number * * @param name * Sets the name of the Value * @param num * The number to store in this Value */ public Value( String name, double num ) { // clearValue(); setValue( num ); setName( name ); } /** * Constructs a new Value of Type VALUE_TYPE_STRING, with a name, containing a String * * @param name * Sets the name of the Value * @param str * The text to store in this Value */ public Value( String name, StringBuffer str ) { this( name, str.toString() ); } /** * Constructs a new Value of Type VALUE_TYPE_STRING, with a name, containing a String * * @param name * Sets the name of the Value * @param str * The text to store in this Value */ public Value( String name, StringBuilder str ) { this( name, str.toString() ); } /** * Constructs a new Value of Type VALUE_TYPE_STRING, with a name, containing a String * * @param name * Sets the name of the Value * @param str * The text to store in this Value */ public Value( String name, String str ) { // clearValue(); setValue( str ); setName( name ); } /** * Constructs a new Value of Type VALUE_TYPE_DATE, with a name, containing a Date * * @param name * Sets the name of the Value * @param dat * The date to store in this Value */ public Value( String name, Date dat ) { // clearValue(); setValue( dat ); setName( name ); } /** * Constructs a new Value of Type VALUE_TYPE_BOOLEAN, with a name, containing a boolean value * * @param name * Sets the name of the Value * @param bool * The boolean to store in this Value */ public Value( String name, boolean bool ) { // clearValue(); setValue( bool ); setName( name ); } /** * Constructs a new Value of Type VALUE_TYPE_INTEGER, with a name, containing an integer number * * @param name * Sets the name of the Value * @param l * The integer to store in this Value */ public Value( String name, long l ) { // clearValue(); setValue( l ); setName( name ); } /** * Constructs a new Value as a copy of another value and renames it... * * @param name * The new name of the copied Value * @param v * The value to be copied */ public Value( String name, Value v ) { this( v ); setName( name ); } /** * Constructs a new Value of Type VALUE_TYPE_BINARY, with a name, containing a bytes value * * @param name * Sets the name of the Value * @param b * The bytes to store in this Value */ public Value( String name, byte[] b ) { clearValue(); setValue( b ); setName( name ); } /** * Constructs a new Value as a copy of another value * * @param v * The Value to be copied */ public Value( Value v ) { if ( v != null ) { // setType(v.getType()); // Is this really needed??? value = v.getValueCopy(); setName( v.getName() ); setLength( v.getLength(), v.getPrecision() ); setNull( v.isNull() ); setOrigin( v.origin ); } else { clearValue(); setNull( true ); } } @Override public Object clone() { Value retval = null; try { retval = (Value) super.clone(); if ( value != null ) { retval.value = (ValueInterface) value.clone(); } } catch ( CloneNotSupportedException e ) { retval = null; } return retval; } /** * Build a copy of this Value * * @return a copy of another value * */ public Value Clone() { Value v = new Value( this ); return v; } /** * Clears the content and name of a Value */ public void clearValue() { value = null; name = null; NULL = false; origin = null; } private ValueInterface getValueCopy() { if ( value == null ) { return null; } return (ValueInterface) value.clone(); } /** * Sets the name of a Value * * @param name * The new name of the value */ public void setName( String name ) { this.name = name; } /** * Obtain the name of a Value * * @return The name of the Value */ public String getName() { return name; } /** * This method allows you to set the origin of the Value by means of the name of the originating step. * * @param step_of_origin * The step of origin. */ public void setOrigin( String step_of_origin ) { origin = step_of_origin; } /** * Obtain the origin of the step. * * @return The name of the originating step */ public String getOrigin() { return origin; } /** * Sets the value to a BigDecimal number value. * * @param num * The number value to set the value to */ public void setValue( BigDecimal num ) { if ( value == null || value.getType() != VALUE_TYPE_BIGNUMBER ) { value = new ValueBigNumber( num ); } else { value.setBigNumber( num ); } setNull( false ); } /** * Sets the value to a double Number value. * * @param num * The number value to set the value to */ public void setValue( double num ) { if ( value == null || value.getType() != VALUE_TYPE_NUMBER ) { value = new ValueNumber( num ); } else { value.setNumber( num ); } setNull( false ); } /** * Sets the Value to a String text * * @param str * The StringBuffer to get the text from */ public void setValue( StringBuffer str ) { if ( value == null || value.getType() != VALUE_TYPE_STRING ) { value = new ValueString( str.toString() ); } else { value.setString( str.toString() ); } setNull( str == null ); } /** * Sets the Value to a String text * * @param str * The StringBuilder to get the text from */ public void setValue( StringBuilder str ) { if ( value == null || value.getType() != VALUE_TYPE_STRING ) { value = new ValueString( str.toString() ); } else { value.setString( str.toString() ); } setNull( str == null ); } /** * Sets the Value to a String text * * @param str * The String to get the text from */ public void setValue( String str ) { if ( value == null || value.getType() != VALUE_TYPE_STRING ) { value = new ValueString( str ); } else { value.setString( str ); } setNull( str == null ); } public void setSerializedValue( Serializable ser ) { if ( value == null || value.getType() != VALUE_TYPE_SERIALIZABLE ) { value = new ValueSerializable( ser ); } else { value.setSerializable( ser ); } setNull( ser == null ); } /** * Sets the Value to a Date * * @param dat * The Date to set the Value to */ public void setValue( Date dat ) { if ( value == null || value.getType() != VALUE_TYPE_DATE ) { value = new ValueDate( dat ); } else { value.setDate( dat ); } setNull( dat == null ); } /** * Sets the Value to a boolean * * @param bool * The boolean to set the Value to */ public void setValue( boolean bool ) { if ( value == null || value.getType() != VALUE_TYPE_BOOLEAN ) { value = new ValueBoolean( bool ); } else { value.setBoolean( bool ); } setNull( false ); } public void setValue( Boolean b ) { setValue( b.booleanValue() ); } /** * Sets the Value to a long integer * * @param b * The byte to convert to a long integer to which the Value is set. */ public void setValue( byte b ) { setValue( (long) b ); } /** * Sets the Value to a long integer * * @param i * The integer to convert to a long integer to which the Value is set. */ public void setValue( int i ) { setValue( (long) i ); } /** * Sets the Value to a long integer * * @param l * The long integer to which the Value is set. */ public void setValue( long l ) { if ( value == null || value.getType() != VALUE_TYPE_INTEGER ) { value = new ValueInteger( l ); } else { value.setInteger( l ); } setNull( false ); } /** * Sets the Value to a byte array * * @param b * The byte array to which the Value has to be set. */ public void setValue( byte[] b ) { if ( value == null || value.getType() != VALUE_TYPE_BINARY ) { value = new ValueBinary( b ); } else { value.setBytes( b ); } if ( b == null ) { setNull( true ); } else { setNull( false ); } } /** * Copy the Value from another Value. It doesn't copy the name. * * @param v * The Value to copy the settings and value from */ public void setValue( Value v ) { if ( v != null ) { value = v.getValueCopy(); setNull( v.isNull() ); setOrigin( v.origin ); } else { clearValue(); } } /** * Get the BigDecimal number of this Value. If the value is not of type BIG_NUMBER, a conversion is done first. * * @return the double precision floating point number of this Value. */ public BigDecimal getBigNumber() { if ( value == null || isNull() ) { return null; } return value.getBigNumber(); } /** * Get the double precision floating point number of this Value. If the value is not of type NUMBER, a conversion is * done first. * * @return the double precision floating point number of this Value. */ public double getNumber() { if ( value == null || isNull() ) { return 0.0; } return value.getNumber(); } /** * Get the String text representing this value. If the value is not of type STRING, a conversion if done first. * * @return the String text representing this value. */ public String getString() { if ( value == null || isNull() ) { return null; } return value.getString(); } /** * Get the length of the String representing this value. * * @return the length of the String representing this value. */ public int getStringLength() { String s = getString(); if ( s == null ) { return 0; } return s.length(); } /** * Get the Date of this Value. If the Value is not of type DATE, a conversion is done first. * * @return the Date of this Value. */ public Date getDate() { if ( value == null || isNull() ) { return null; } return value.getDate(); } /** * Get the Serializable of this Value. If the Value is not of type Serializable, it returns null. * * @return the Serializable of this Value. */ public Serializable getSerializable() { if ( value == null || isNull() || value.getType() != VALUE_TYPE_SERIALIZABLE ) { return null; } return value.getSerializable(); } /** * Get the boolean value of this Value. If the Value is not of type BOOLEAN, it will be converted. * <p> * Strings: "YES", "Y", "TRUE" (case insensitive) to true, the rest false * <p> * Number: 0.0 is false, the rest is true. * <p> * Integer: 0 is false, the rest is true. * <p> * Date: always false. * * @return the boolean representation of this Value. */ public boolean getBoolean() { if ( value == null || isNull() ) { return false; } return value.getBoolean(); } /** * Get the long integer representation of this value. If the Value is not of type INTEGER, it will be converted: * <p> * String: try to convert to a long value, 0L if it didn't work. * <p> * Number: round the double value and return the resulting long integer. * <p> * Date: return the number of miliseconds after <code>1970:01:01 00:00:00</code> * <p> * Date: always false. * * @return the long integer representation of this value. */ public long getInteger() { if ( value == null || isNull() ) { return 0L; } return value.getInteger(); } public byte[] getBytes() { if ( value == null || isNull() ) { return null; } return value.getBytes(); } /** * Set the type of this Value * * @param val_type * The type to which the Value will be set. */ public void setType( int val_type ) { if ( value == null ) { newValue( val_type ); } else { // Convert the value to the appropriate type... convertTo( val_type ); } } /** * Returns the type of this Value * * @return the type of this Value */ public int getType() { if ( value == null ) { return VALUE_TYPE_NONE; } return value.getType(); } /** * Checks whether or not this Value is empty. A value is empty if it has the type VALUE_TYPE_EMPTY * * @return true if the value is empty. */ public boolean isEmpty() { if ( value == null ) { return true; } return false; } /** * Checks wheter or not the value is a String. * * @return true if the value is a String. */ public boolean isString() { if ( value == null ) { return false; } return value.getType() == VALUE_TYPE_STRING; } /** * Checks whether or not this value is a Date * * @return true if the value is a Date */ public boolean isDate() { if ( value == null ) { return false; } return value.getType() == VALUE_TYPE_DATE; } /** * Checks whether or not the value is a Big Number * * @return true is this value is a big number */ public boolean isBigNumber() { if ( value == null ) { return false; } return value.getType() == VALUE_TYPE_BIGNUMBER; } /** * Checks whether or not the value is a Number * * @return true is this value is a number */ public boolean isNumber() { if ( value == null ) { return false; } return value.getType() == VALUE_TYPE_NUMBER; } /** * Checks whether or not this value is a boolean * * @return true if this value has type boolean. */ public boolean isBoolean() { if ( value == null ) { return false; } return value.getType() == VALUE_TYPE_BOOLEAN; } /** * Checks whether or not this value is of type Serializable * * @return true if this value has type Serializable */ public boolean isSerializableType() { if ( value == null ) { return false; } return value.getType() == VALUE_TYPE_SERIALIZABLE; } /** * Checks whether or not this value is of type Binary * * @return true if this value has type Binary */ public boolean isBinary() { // Serializable is not included here as it used for // internal purposes only. if ( value == null ) { return false; } return value.getType() == VALUE_TYPE_BINARY; } /** * Checks whether or not this value is an Integer * * @return true if this value is an integer */ public boolean isInteger() { if ( value == null ) { return false; } return value.getType() == VALUE_TYPE_INTEGER; } /** * Checks whether or not this Value is Numeric A Value is numeric if it is either of type Number or Integer * * @return true if the value is either of type Number or Integer */ public boolean isNumeric() { return isInteger() || isNumber() || isBigNumber(); } /** * Checks whether or not the specified type is either Integer or Number * * @param t * the type to check * @return true if the type is Integer or Number */ public static final boolean isNumeric( int t ) { return t == VALUE_TYPE_INTEGER || t == VALUE_TYPE_NUMBER || t == VALUE_TYPE_BIGNUMBER; } /** * Returns a padded to length String text representation of this Value * * @return a padded to length String text representation of this Value */ @Override public String toString() { return toString( true ); } /** * a String text representation of this Value, optionally padded to the specified length * * @param pad * true if you want to pad the resulting String * @return a String text representation of this Value, optionally padded to the specified length */ public String toString( boolean pad ) { String retval; switch ( getType() ) { case VALUE_TYPE_STRING: retval = toStringString( pad ); break; case VALUE_TYPE_INTEGER: retval = toStringInteger( pad ); break; case VALUE_TYPE_NUMBER: retval = toStringNumber( pad ); break; case VALUE_TYPE_DATE: retval = toStringDate(); break; case VALUE_TYPE_BOOLEAN: retval = toStringBoolean(); break; case VALUE_TYPE_BIGNUMBER: retval = toStringBigNumber(); break; case VALUE_TYPE_BINARY: retval = toStringBinary(); break; default: retval = ""; break; } return retval; } /** * a String text representation of this Value, optionally padded to the specified length * * @return a String text representation of this Value, optionally padded to the specified length */ public String toStringMeta() { // We (Sven Boden) did explicit performance testing for this // part. The original version used Strings instead of StringBuffers, // performance between the 2 does not differ that much. A few milliseconds // on 100000 iterations in the advantage of StringBuffers. The // lessened creation of objects may be worth it in the long run. // Marc - StringBuffer was replaced with StringBuilder for performance reasons. // No need for a StringBuffer (which is synchronized ) StringBuilder retval = new StringBuilder( getTypeDesc() ); switch ( getType() ) { case VALUE_TYPE_STRING: if ( getLength() > 0 ) { retval.append( '(' ).append( getLength() ).append( ')' ); } break; case VALUE_TYPE_NUMBER: case VALUE_TYPE_BIGNUMBER: if ( getLength() > 0 ) { retval.append( '(' ).append( getLength() ); if ( getPrecision() > 0 ) { retval.append( ", " ).append( getPrecision() ); } retval.append( ')' ); } break; case VALUE_TYPE_INTEGER: if ( getLength() > 0 ) { retval.append( '(' ).append( getLength() ).append( ')' ); } break; default: break; } return retval.toString(); } /** * Converts a String Value to String optionally padded to the specified length. * * @param pad * true if you want to pad the resulting string to length. * @return a String optionally padded to the specified length. */ private String toStringString( boolean pad ) { String retval = null; if ( value == null ) { return null; } if ( value.getLength() <= 0 ) { // No length specified! if ( isNull() || value.getString() == null ) { retval = Const.NULL_STRING; } else { retval = value.getString(); } } else { if ( pad ) { StringBuilder ret = null; if ( isNull() || value.getString() == null ) { ret = new StringBuilder( Const.NULL_STRING ); } else { ret = new StringBuilder( value.getString() ); } int length = value.getLength(); if ( length > 16384 ) { length = 16384; // otherwise we get OUT OF MEMORY errors for CLOBS. } Const.rightPad( ret, length ); retval = ret.toString(); } else { if ( isNull() || value.getString() == null ) { retval = Const.NULL_STRING; } else { retval = value.getString(); } } } return retval; } /** * Converts a Number value to a String, optionally padding the result to the specified length. * * @param pad * true if you want to pad the resulting string to length. * @return a String optionally padded to the specified length. */ private String toStringNumber( boolean pad ) { String retval; if ( value == null ) { return null; } if ( pad ) { if ( value.getLength() < 1 ) { if ( isNull() ) { retval = Const.NULL_NUMBER; } else { DecimalFormat form = new DecimalFormat(); form.applyPattern( " ##########0.0########;-#########0.0########" ); // System.out.println("local.pattern = ["+form.toLocalizedPattern()+"]"); retval = form.format( value.getNumber() ); } } else { if ( isNull() ) { StringBuilder ret = new StringBuilder( Const.NULL_NUMBER ); Const.rightPad( ret, value.getLength() ); retval = ret.toString(); } else { StringBuilder fmt = new StringBuilder(); int i; DecimalFormat form; if ( value.getNumber() >= 0 ) { fmt.append( ' ' ); // to compensate for minus sign. } if ( value.getPrecision() < 0 ) { // Default: two decimals for ( i = 0; i < value.getLength(); i++ ) { fmt.append( '0' ); } fmt.append( ".00" ); // for the .00 } else { // Floating point format 00001234,56 --> (12,2) for ( i = 0; i <= value.getLength(); i++ ) { fmt.append( '0' ); // all zeroes. } int pos = value.getLength() - value.getPrecision() + 1 - ( value.getNumber() < 0 ? 1 : 0 ); if ( pos >= 0 && pos < fmt.length() ) { // one 'comma' fmt .setCharAt( value.getLength() - value.getPrecision() + 1 - ( value.getNumber() < 0 ? 1 : 0 ), '.' ); } } form = new DecimalFormat( fmt.toString() ); retval = form.format( value.getNumber() ); } } } else { if ( isNull() ) { retval = Const.NULL_NUMBER; } else { retval = Double.toString( value.getNumber() ); } } return retval; } /** * Converts a Date value to a String. The date has format: <code>yyyy/MM/dd HH:mm:ss.SSS</code> * * @return a String representing the Date Value. */ private String toStringDate() { String retval; if ( value == null ) { return null; } SimpleDateFormat df = new SimpleDateFormat( "yyyy/MM/dd HH:mm:ss.SSS", Locale.US ); if ( isNull() || value.getDate() == null ) { retval = Const.NULL_DATE; } else { retval = df.format( value.getDate() ).toString(); } /* * This code was removed as TYPE_VALUE_DATE does not know "length", so this could never be called anyway else { * StringBuffer ret; if (isNull() || value.getDate()==null) ret=new StringBuffer(Const.NULL_DATE); else ret=new * StringBuffer(df.format(value.getDate()).toString()); Const.rightPad(ret, getLength()<=10?10:getLength()); * retval=ret.toString(); } */ return retval; } /** * Returns a String representing the boolean value. It will be either "true" or "false". * * @return a String representing the boolean value. */ private String toStringBoolean() { // Code was removed from this method as ValueBoolean // did not store length, so some parts could never be // called. String retval; if ( value == null ) { return null; } if ( isNull() ) { retval = Const.NULL_BOOLEAN; } else { retval = value.getBoolean() ? "true" : "false"; } return retval; } /** * Converts an Integer value to a String, optionally padding the result to the specified length. * * @param pad * true if you want to pad the resulting string to length. * @return a String optionally padded to the specified length. */ private String toStringInteger( boolean pad ) { String retval; if ( value == null ) { return null; } if ( getLength() < 1 ) { if ( isNull() ) { retval = Const.NULL_INTEGER; } else { DecimalFormat form = new DecimalFormat( " ###############0;-###############0" ); retval = form.format( value.getInteger() ); } } else { if ( isNull() ) { StringBuilder ret = new StringBuilder( Const.NULL_INTEGER ); Const.rightPad( ret, getLength() ); retval = ret.toString(); } else { if ( pad ) { StringBuilder fmt = new StringBuilder(); int i; DecimalFormat form; if ( value.getInteger() >= 0 ) { fmt.append( ' ' ); // to compensate for minus sign. } int len = getLength(); for ( i = 0; i < len; i++ ) { fmt.append( '0' ); // all zeroes. } form = new DecimalFormat( fmt.toString() ); retval = form.format( value.getInteger() ); } else { retval = Long.toString( value.getInteger() ); } } } return retval; } /** * Converts a BigNumber value to a String, optionally padding the result to the specified length. * * @param pad * true if you want to pad the resulting string to length. * @return a String optionally padded to the specified length. */ private String toStringBigNumber() { if ( value == null ) { return null; } String retval; if ( isNull() ) { retval = Const.NULL_BIGNUMBER; } else { if ( value.getBigNumber() == null ) { retval = null; } else { retval = value.getString(); // Localise . to , if ( Const.DEFAULT_DECIMAL_SEPARATOR != '.' ) { retval = retval.replace( '.', Const.DEFAULT_DECIMAL_SEPARATOR ); } } } return retval; } /** * Returns a String representing the binary value. * * @return a String representing the binary value. */ private String toStringBinary() { String retval; if ( value == null ) { return null; } if ( isNull() || value.getBytes() == null ) { retval = Const.NULL_BINARY; } else { retval = new String( value.getBytes() ); } return retval; } /** * Sets the length of the Number, Integer or String to the specified length Note: no truncation of the value takes * place, this is meta-data only! * * @param l * the length to which you want to set the Value. */ public void setLength( int l ) { if ( value == null ) { return; } value.setLength( l ); } /** * Sets the length and the precision of the Number, Integer or String to the specified length & precision Note: no * truncation of the value takes place, this is meta-data only! * * @param l * the length to which you want to set the Value. * @param p * the precision to which you want to set this Value */ public void setLength( int l, int p ) { if ( value == null ) { return; } value.setLength( l, p ); } /** * Get the length of this Value. * * @return the length of this Value. */ public int getLength() { if ( value == null ) { return -1; } return value.getLength(); } /** * get the precision of this Value * * @return the precision of this Value. */ public int getPrecision() { if ( value == null ) { return -1; } return value.getPrecision(); } /** * Sets the precision of this Value Note: no rounding or truncation takes place, this is meta-data only! * * @param p * the precision to which you want to set this Value. */ public void setPrecision( int p ) { if ( value == null ) { return; } value.setPrecision( p ); } /** * Return the type of a value in a textual form: "String", "Number", "Integer", "Boolean", "Date", ... * * @return A String describing the type of value. */ public String getTypeDesc() { if ( value == null ) { return "Unknown"; } return value.getTypeDesc(); } /** * Return the type of a value in a textual form: "String", "Number", "Integer", "Boolean", "Date", ... given a certain * integer type * * @param t * the type to convert to text. * @return A String describing the type of a certain value. */ public static final String getTypeDesc( int t ) { return valueTypeCode[t]; } /** * Convert the String description of a type to an integer type. * * @param desc * The description of the type to convert * @return The integer type of the given String. (Value.VALUE_TYPE_...) */ public static final int getType( String desc ) { int i; for ( i = 1; i < valueTypeCode.length; i++ ) { if ( valueTypeCode[i].equalsIgnoreCase( desc ) ) { return i; } } return VALUE_TYPE_NONE; } /** * get an array of String describing the possible types a Value can have. * * @return an array of String describing the possible types a Value can have. */ public static final String[] getTypes() { String[] retval = new String[valueTypeCode.length - 1]; System.arraycopy( valueTypeCode, 1, retval, 0, valueTypeCode.length - 1 ); return retval; } /** * Get an array of String describing the possible types a Value can have. * * @return an array of String describing the possible types a Value can have. */ public static final String[] getAllTypes() { String[] retval = new String[valueTypeCode.length]; System.arraycopy( valueTypeCode, 0, retval, 0, valueTypeCode.length ); return retval; } /** * Sets the Value to null, no type is being changed. * */ public void setNull() { setNull( true ); } /** * Sets or unsets a value to null, no type is being changed. * * @param n * true if you want the value to be null, false if you don't want this to be the case. */ public void setNull( boolean n ) { NULL = n; } /** * Checks wheter or not a value is null. * * @return true if the Value is null. */ public boolean isNull() { return NULL; } /** * Write the object to an ObjectOutputStream * * @param out * @throws IOException */ private void writeObject( java.io.ObjectOutputStream out ) throws IOException { writeObj( new DataOutputStream( out ) ); } private void readObject( java.io.ObjectInputStream in ) throws IOException { readObj( new DataInputStream( in ) ); } public void writeObj( DataOutputStream dos ) throws IOException { int type = getType(); // Handle type dos.writeInt( getType() ); // Handle name-length dos.writeInt( name.length() ); // Write name dos.writeChars( name ); // length & precision dos.writeInt( getLength() ); dos.writeInt( getPrecision() ); // NULL? dos.writeBoolean( isNull() ); // Handle Content -- only when not NULL if ( !isNull() ) { switch ( type ) { case VALUE_TYPE_STRING: if ( getString() == null ) { dos.writeInt( -1 ); // -1 == null string } else { String string = getString(); byte[] chars = string.getBytes( Const.XML_ENCODING ); dos.writeInt( chars.length ); dos.write( chars ); } break; case VALUE_TYPE_BIGNUMBER: if ( getBigNumber() == null ) { dos.writeInt( -1 ); // -1 == null string } else { String string = getBigNumber().toString(); dos.writeInt( string.length() ); dos.writeChars( string ); } break; case VALUE_TYPE_DATE: dos.writeBoolean( getDate() != null ); if ( getDate() != null ) { dos.writeLong( getDate().getTime() ); } break; case VALUE_TYPE_NUMBER: dos.writeDouble( getNumber() ); break; case VALUE_TYPE_BOOLEAN: dos.writeBoolean( getBoolean() ); break; case VALUE_TYPE_INTEGER: dos.writeLong( getInteger() ); break; default: break; // nothing } } } /** * Write the value, including the meta-data to a DataOutputStream * * @param outputStream * the OutputStream to write to . * @throws KettleFileException * if something goes wrong. */ public void write( OutputStream outputStream ) throws KettleFileException { try { writeObj( new DataOutputStream( outputStream ) ); } catch ( Exception e ) { throw new KettleFileException( "Unable to write value to output stream", e ); } } /** * Read the metadata and data for this Value object from the specified data input stream * * @param dis * @throws IOException */ public void readObj( DataInputStream dis ) throws IOException { // type int theType = dis.readInt(); newValue( theType ); // name-length int nameLength = dis.readInt(); // name StringBuilder nameBuffer = new StringBuilder(); for ( int i = 0; i < nameLength; i++ ) { nameBuffer.append( dis.readChar() ); } setName( new String( nameBuffer ) ); // length & precision setLength( dis.readInt(), dis.readInt() ); // Null? setNull( dis.readBoolean() ); // Read the values if ( !isNull() ) { switch ( getType() ) { case VALUE_TYPE_STRING: // read the length int stringLength = dis.readInt(); if ( stringLength < 0 ) { setValue( (String) null ); } else { byte[] chars = new byte[stringLength]; dis.readFully( chars ); setValue( new String( chars, Const.XML_ENCODING ) ); } break; case VALUE_TYPE_BIGNUMBER: // read the length int bnLength = dis.readInt(); if ( bnLength < 0 ) { setValue( (BigDecimal) null ); } else { StringBuilder buffer = new StringBuilder(); for ( int i = 0; i < bnLength; i++ ) { buffer.append( dis.readChar() ); } setValue( buffer.toString() ); try { convertString( VALUE_TYPE_BIGNUMBER ); } catch ( KettleValueException e ) { throw new IOException( "Unable to convert String to BigNumber while reading from data input stream [" + getString() + "]" ); } } break; case VALUE_TYPE_DATE: if ( dis.readBoolean() ) { setValue( new Date( dis.readLong() ) ); } break; case VALUE_TYPE_NUMBER: setValue( dis.readDouble() ); break; case VALUE_TYPE_INTEGER: setValue( dis.readLong() ); break; case VALUE_TYPE_BOOLEAN: setValue( dis.readBoolean() ); break; default: break; } } } /** * Read the Value, including meta-data from a DataInputStream * * @param is * The InputStream to read the value from * @throws KettleFileException * when the Value couldn't be created by reading it from the DataInputStream. */ public Value( InputStream is ) throws KettleFileException { try { readObj( new DataInputStream( is ) ); } catch ( EOFException e ) { throw new KettleEOFException( "End of file reached", e ); } catch ( Exception e ) { throw new KettleFileException( "Error reading from data input stream", e ); } } /** * Write the data of this Value, without the meta-data to a DataOutputStream * * @param dos * The DataOutputStream to write the data to * @return true if all went well, false if something went wrong. */ public boolean writeData( DataOutputStream dos ) throws KettleFileException { try { // Is the value NULL? dos.writeBoolean( isNull() ); // Handle Content -- only when not NULL if ( !isNull() ) { switch ( getType() ) { case VALUE_TYPE_STRING: if ( getString() == null ) { dos.writeInt( -1 ); // -1 == null string } else { String string = getString(); byte[] chars = string.getBytes( Const.XML_ENCODING ); dos.writeInt( chars.length ); dos.write( chars ); } break; case VALUE_TYPE_BIGNUMBER: if ( getBigNumber() == null ) { dos.writeInt( -1 ); // -1 == null big number } else { String string = getBigNumber().toString(); dos.writeInt( string.length() ); dos.writeChars( string ); } break; case VALUE_TYPE_DATE: dos.writeBoolean( getDate() != null ); if ( getDate() != null ) { dos.writeLong( getDate().getTime() ); } break; case VALUE_TYPE_NUMBER: dos.writeDouble( getNumber() ); break; case VALUE_TYPE_BOOLEAN: dos.writeBoolean( getBoolean() ); break; case VALUE_TYPE_INTEGER: dos.writeLong( getInteger() ); break; default: break; // nothing } } } catch ( IOException e ) { throw new KettleFileException( "Unable to write value data to output stream", e ); } return true; } /** * Read the data of a Value from a DataInputStream, the meta-data of the value has to be set before calling this * method! * * @param dis * the DataInputStream to read from * @throws KettleFileException * when the value couldn't be read from the DataInputStream */ public Value( Value metaData, DataInputStream dis ) throws KettleFileException { setValue( metaData ); setName( metaData.getName() ); try { // Is the value NULL? setNull( dis.readBoolean() ); // Read the values if ( !isNull() ) { switch ( getType() ) { case VALUE_TYPE_STRING: // read the length int stringLength = dis.readInt(); if ( stringLength < 0 ) { setValue( (String) null ); } else { byte[] chars = new byte[stringLength]; dis.readFully( chars ); setValue( new String( chars, Const.XML_ENCODING ) ); } break; case VALUE_TYPE_BIGNUMBER: // read the length int bnLength = dis.readInt(); if ( bnLength < 0 ) { setValue( (BigDecimal) null ); } else { StringBuilder buffer = new StringBuilder(); for ( int i = 0; i < bnLength; i++ ) { buffer.append( dis.readChar() ); } setValue( buffer.toString() ); try { convertString( VALUE_TYPE_BIGNUMBER ); } catch ( KettleValueException e ) { throw new IOException( "Unable to convert String to BigNumber while reading from data input stream [" + getString() + "]" ); } } break; case VALUE_TYPE_DATE: if ( dis.readBoolean() ) { setValue( new Date( dis.readLong() ) ); } break; case VALUE_TYPE_NUMBER: setValue( dis.readDouble() ); break; case VALUE_TYPE_INTEGER: setValue( dis.readLong() ); break; case VALUE_TYPE_BOOLEAN: setValue( dis.readBoolean() ); break; default: break; } } } catch ( EOFException e ) { throw new KettleEOFException( "End of file reached while reading value", e ); } catch ( Exception e ) { throw new KettleEOFException( "Error reading value data from stream", e ); } } /** * Compare 2 values of the same or different type! The comparison of Strings is case insensitive * * @param v * the value to compare with. * @return -1 if The value was smaller, 1 bigger and 0 if both values are equal. */ public int compare( Value v ) { return compare( v, true ); } /** * Compare 2 values of the same or different type! * * @param v * the value to compare with. * @param caseInsensitive * True if you want the comparison to be case insensitive * @return -1 if The value was smaller, 1 bigger and 0 if both values are equal. */ public int compare( Value v, boolean caseInsensitive ) { boolean n1 = isNull() || ( isString() && ( getString() == null || getString().length() == 0 ) ) || ( isDate() && getDate() == null ) || ( isBigNumber() && getBigNumber() == null ); boolean n2 = v.isNull() || ( v.isString() && ( v.getString() == null || v.getString().length() == 0 ) ) || ( v.isDate() && v.getDate() == null ) || ( v.isBigNumber() && v.getBigNumber() == null ); // null is always smaller! if ( n1 && !n2 ) { return -1; } if ( !n1 && n2 ) { return 1; } if ( n1 && n2 ) { return 0; } switch ( getType() ) { case VALUE_TYPE_STRING: { String one = Const.rtrim( getString() ); String two = Const.rtrim( v.getString() ); int cmp = 0; if ( caseInsensitive ) { cmp = one.compareToIgnoreCase( two ); } else { cmp = one.compareTo( two ); } return cmp; } case VALUE_TYPE_INTEGER: { return Double.compare( getNumber(), v.getNumber() ); } case VALUE_TYPE_DATE: { return Double.compare( getNumber(), v.getNumber() ); } case VALUE_TYPE_BOOLEAN: { if ( getBoolean() && v.getBoolean() || !getBoolean() && !v.getBoolean() ) { return 0; // true == true, false == false } if ( getBoolean() && !v.getBoolean() ) { return 1; // true > false } return -1; // false < true } case VALUE_TYPE_NUMBER: { return Double.compare( getNumber(), v.getNumber() ); } case VALUE_TYPE_BIGNUMBER: { return getBigNumber().compareTo( v.getBigNumber() ); } default: break; } // Still here? Not possible! But hey, give back 0, mkay? return 0; } @Override public boolean equals( Object v ) { if ( compare( (Value) v ) == 0 ) { return true; } else { return false; } } /** * Check whether this value is equal to the String supplied. * * @param string * The string to check for equality * @return true if the String representation of the value is equal to string. (ignoring case) */ public boolean isEqualTo( String string ) { return getString().equalsIgnoreCase( string ); } /** * Check whether this value is equal to the BigDecimal supplied. * * @param number * The BigDecimal to check for equality * @return true if the BigDecimal representation of the value is equal to number. */ public boolean isEqualTo( BigDecimal number ) { return getBigNumber().equals( number ); } /** * Check whether this value is equal to the Number supplied. * * @param number * The Number to check for equality * @return true if the Number representation of the value is equal to number. */ public boolean isEqualTo( double number ) { return getNumber() == number; } /** * Check whether this value is equal to the Integer supplied. * * @param number * The Integer to check for equality * @return true if the Integer representation of the value is equal to number. */ public boolean isEqualTo( long number ) { return getInteger() == number; } /** * Check whether this value is equal to the Integer supplied. * * @param number * The Integer to check for equality * @return true if the Integer representation of the value is equal to number. */ public boolean isEqualTo( int number ) { return getInteger() == number; } /** * Check whether this value is equal to the Integer supplied. * * @param number * The Integer to check for equality * @return true if the Integer representation of the value is equal to number. */ public boolean isEqualTo( byte number ) { return getInteger() == number; } /** * Check whether this value is equal to the Date supplied. * * @param date * The Date to check for equality * @return true if the Date representation of the value is equal to date. */ public boolean isEqualTo( Date date ) { return getDate() == date; } @Override public int hashCode() { int hash = 0; // name.hashCode(); -> Name shouldn't be part of hashCode()! if ( isNull() ) { switch ( getType() ) { case VALUE_TYPE_BOOLEAN: hash ^= 1; break; case VALUE_TYPE_DATE: hash ^= 2; break; case VALUE_TYPE_NUMBER: hash ^= 4; break; case VALUE_TYPE_STRING: hash ^= 8; break; case VALUE_TYPE_INTEGER: hash ^= 16; break; case VALUE_TYPE_BIGNUMBER: hash ^= 32; break; case VALUE_TYPE_NONE: break; default: break; } } else { switch ( getType() ) { case VALUE_TYPE_BOOLEAN: hash ^= Boolean.valueOf( getBoolean() ).hashCode(); break; case VALUE_TYPE_DATE: if ( getDate() != null ) { hash ^= getDate().hashCode(); } break; case VALUE_TYPE_INTEGER: hash ^= new Long( getInteger() ).hashCode(); break; case VALUE_TYPE_NUMBER: hash ^= ( new Double( getNumber() ) ).hashCode(); break; case VALUE_TYPE_STRING: if ( getString() != null ) { hash ^= getString().hashCode(); } break; case VALUE_TYPE_BIGNUMBER: if ( getBigNumber() != null ) { hash ^= getBigNumber().hashCode(); } break; case VALUE_TYPE_NONE: break; default: break; } } return hash; } // OPERATORS & COMPARATORS public Value and( Value v ) { long n1 = getInteger(); long n2 = v.getInteger(); long res = n1 & n2; setValue( res ); return this; } public Value xor( Value v ) { long n1 = getInteger(); long n2 = v.getInteger(); long res = n1 ^ n2; setValue( res ); return this; } public Value or( Value v ) { long n1 = getInteger(); long n2 = v.getInteger(); long res = n1 | n2; setValue( res ); return this; } public Value bool_and( Value v ) { boolean b1 = getBoolean(); boolean b2 = v.getBoolean(); boolean res = b1 && b2; setValue( res ); return this; } public Value bool_or( Value v ) { boolean b1 = getBoolean(); boolean b2 = v.getBoolean(); boolean res = b1 || b2; setValue( res ); return this; } public Value bool_xor( Value v ) { boolean b1 = getBoolean(); boolean b2 = v.getBoolean(); boolean res = b1 && b2 ? false : !b1 && !b2 ? false : true; setValue( res ); return this; } public Value bool_not() { value.setBoolean( !getBoolean() ); return this; } public Value greater_equal( Value v ) { if ( compare( v ) >= 0 ) { setValue( true ); } else { setValue( false ); } return this; } public Value smaller_equal( Value v ) { if ( compare( v ) <= 0 ) { setValue( true ); } else { setValue( false ); } return this; } public Value different( Value v ) { if ( compare( v ) != 0 ) { setValue( true ); } else { setValue( false ); } return this; } public Value equal( Value v ) { if ( compare( v ) == 0 ) { setValue( true ); } else { setValue( false ); } return this; } public Value like( Value v ) { String cmp = v.getString(); // Is cmp part of look? int idx = getString().indexOf( cmp ); if ( idx < 0 ) { setValue( false ); } else { setValue( true ); } return this; } public Value greater( Value v ) { if ( compare( v ) > 0 ) { setValue( true ); } else { setValue( false ); } return this; } public Value smaller( Value v ) { if ( compare( v ) < 0 ) { setValue( true ); } else { setValue( false ); } return this; } public Value minus( BigDecimal v ) throws KettleValueException { return minus( new Value( "tmp", v ) ); } public Value minus( double v ) throws KettleValueException { return minus( new Value( "tmp", v ) ); } public Value minus( long v ) throws KettleValueException { return minus( new Value( "tmp", v ) ); } public Value minus( int v ) throws KettleValueException { return minus( new Value( "tmp", (long) v ) ); } public Value minus( byte v ) throws KettleValueException { return minus( new Value( "tmp", (long) v ) ); } public Value minus( Value v ) throws KettleValueException { switch ( getType() ) { case VALUE_TYPE_BIGNUMBER: value.setBigNumber( getBigNumber().subtract( v.getBigNumber() ) ); break; case VALUE_TYPE_NUMBER: value.setNumber( getNumber() - v.getNumber() ); break; case VALUE_TYPE_INTEGER: value.setInteger( getInteger() - v.getInteger() ); break; case VALUE_TYPE_BOOLEAN: case VALUE_TYPE_STRING: default: throw new KettleValueException( "Subtraction can only be done with numbers!" ); } return this; } public Value plus( BigDecimal v ) { return plus( new Value( "tmp", v ) ); } public Value plus( double v ) { return plus( new Value( "tmp", v ) ); } public Value plus( long v ) { return plus( new Value( "tmp", v ) ); } public Value plus( int v ) { return plus( new Value( "tmp", (long) v ) ); } public Value plus( byte v ) { return plus( new Value( "tmp", (long) v ) ); } public Value plus( Value v ) { switch ( getType() ) { case VALUE_TYPE_BIGNUMBER: setValue( getBigNumber().add( v.getBigNumber() ) ); break; case VALUE_TYPE_NUMBER: setValue( getNumber() + v.getNumber() ); break; case VALUE_TYPE_INTEGER: setValue( getInteger() + v.getInteger() ); break; case VALUE_TYPE_BOOLEAN: setValue( getBoolean() | v.getBoolean() ); break; case VALUE_TYPE_STRING: setValue( getString() + v.getString() ); break; default: break; } return this; } public Value divide( BigDecimal v ) throws KettleValueException { return divide( new Value( "tmp", v ) ); } public Value divide( double v ) throws KettleValueException { return divide( new Value( "tmp", v ) ); } public Value divide( long v ) throws KettleValueException { return divide( new Value( "tmp", v ) ); } public Value divide( int v ) throws KettleValueException { return divide( new Value( "tmp", (long) v ) ); } public Value divide( byte v ) throws KettleValueException { return divide( new Value( "tmp", (long) v ) ); } public Value divide( Value v ) throws KettleValueException { if ( isNull() || v.isNull() ) { setNull(); } else { switch ( getType() ) { case VALUE_TYPE_BIGNUMBER: setValue( getBigNumber().divide( v.getBigNumber(), BigDecimal.ROUND_HALF_UP ) ); break; case VALUE_TYPE_NUMBER: setValue( getNumber() / v.getNumber() ); break; case VALUE_TYPE_INTEGER: setValue( getInteger() / v.getInteger() ); break; case VALUE_TYPE_BOOLEAN: case VALUE_TYPE_STRING: default: throw new KettleValueException( "Division can only be done with numeric data!" ); } } return this; } public Value multiply( BigDecimal v ) throws KettleValueException { return multiply( new Value( "tmp", v ) ); } public Value multiply( double v ) throws KettleValueException { return multiply( new Value( "tmp", v ) ); } public Value multiply( long v ) throws KettleValueException { return multiply( new Value( "tmp", v ) ); } public Value multiply( int v ) throws KettleValueException { return multiply( new Value( "tmp", (long) v ) ); } public Value multiply( byte v ) throws KettleValueException { return multiply( new Value( "tmp", (long) v ) ); } public Value multiply( Value v ) throws KettleValueException { // a number and a string! if ( isNull() || v.isNull() ) { setNull(); return this; } if ( ( v.isString() && isNumeric() ) || ( v.isNumeric() && isString() ) ) { StringBuilder s; String append = ""; int n; if ( v.isString() ) { s = new StringBuilder( v.getString() ); append = v.getString(); n = (int) getInteger(); } else { s = new StringBuilder( getString() ); append = getString(); n = (int) v.getInteger(); } if ( n == 0 ) { s.setLength( 0 ); } else { for ( int i = 1; i < n; i++ ) { s.append( append ); } } setValue( s ); } else if ( isBigNumber() || v.isBigNumber() ) { // big numbers setValue( ValueDataUtil.multiplyBigDecimals( getBigNumber(), v.getBigNumber(), null ) ); } else if ( isNumber() || v.isNumber() ) { // numbers setValue( getNumber() * v.getNumber() ); } else if ( isInteger() || v.isInteger() ) { // integers setValue( getInteger() * v.getInteger() ); } else { throw new KettleValueException( "Multiplication can only be done with numbers or a number and a string!" ); } return this; } // FUNCTIONS!! // implement the ABS function, arguments in args[] public Value abs() throws KettleValueException { if ( isNull() ) { return this; } if ( isBigNumber() ) { setValue( getBigNumber().abs() ); } else if ( isNumber() ) { setValue( Math.abs( getNumber() ) ); } else if ( isInteger() ) { setValue( Math.abs( getInteger() ) ); } else { throw new KettleValueException( "Function ABS only works with a number" ); } return this; } // implement the ACOS function, arguments in args[] public Value acos() throws KettleValueException { if ( isNull() ) { return this; } if ( isNumeric() ) { setValue( Math.acos( getNumber() ) ); } else { throw new KettleValueException( "Function ACOS only works with numeric data" ); } return this; } // implement the ASIN function, arguments in args[] public Value asin() throws KettleValueException { if ( isNull() ) { return this; } if ( isNumeric() ) { setValue( Math.asin( getNumber() ) ); } else { throw new KettleValueException( "Function ASIN only works with numeric data" ); } return this; } // implement the ATAN function, arguments in args[] public Value atan() throws KettleValueException { if ( isNull() ) { return this; } if ( isNumeric() ) { setValue( Math.atan( getNumber() ) ); } else { throw new KettleValueException( "Function ATAN only works with numeric data" ); } return this; } // implement the ATAN2 function, arguments in args[] public Value atan2( Value arg0 ) throws KettleValueException { return atan2( arg0.getNumber() ); } public Value atan2( double arg0 ) throws KettleValueException { if ( isNull() ) { return this; } if ( isNumeric() ) { setValue( Math.atan2( getNumber(), arg0 ) ); } else { throw new KettleValueException( "Function ATAN2 only works with numbers" ); } return this; } // implement the CEIL function, arguments in args[] public Value ceil() throws KettleValueException { if ( isNull() ) { return this; } if ( isNumeric() ) { setValue( Math.ceil( getNumber() ) ); } else { throw new KettleValueException( "Function CEIL only works with a number" ); } return this; } // implement the COS function, arguments in args[] public Value cos() throws KettleValueException { if ( isNull() ) { return this; } if ( isNumeric() ) { setValue( Math.cos( getNumber() ) ); } else { throw new KettleValueException( "Function COS only works with a number" ); } return this; } // implement the EXP function, arguments in args[] public Value exp() throws KettleValueException { if ( isNull() ) { return this; } if ( isNumeric() ) { setValue( Math.exp( getNumber() ) ); } else { throw new KettleValueException( "Function EXP only works with a number" ); } return this; } // implement the FLOOR function, arguments in args[] public Value floor() throws KettleValueException { if ( isNull() ) { return this; } if ( isNumeric() ) { setValue( Math.floor( getNumber() ) ); } else { throw new KettleValueException( "Function FLOOR only works with a number" ); } return this; } // implement the INITCAP function, arguments in args[] public Value initcap() { if ( isNull() ) { return this; } if ( getString() == null ) { setNull(); } else { setValue( Const.initCap( getString() ) ); } return this; } // implement the LENGTH function, arguments in args[] public Value length() throws KettleValueException { if ( isNull() ) { setType( VALUE_TYPE_INTEGER ); setValue( 0L ); return this; } if ( getType() == VALUE_TYPE_STRING ) { setValue( (double) getString().length() ); } else { throw new KettleValueException( "Function LENGTH only works with a string" ); } return this; } // implement the LOG function, arguments in args[] public Value log() throws KettleValueException { if ( isNull() ) { return this; } if ( isNumeric() ) { setValue( Math.log( getNumber() ) ); } else { throw new KettleValueException( "Function LOG only works with a number" ); } return this; } // implement the LOWER function, arguments in args[] public Value lower() { if ( isNull() ) { setType( VALUE_TYPE_STRING ); } else { setValue( getString().toLowerCase() ); } return this; } // implement the LPAD function: left pad strings or numbers... public Value lpad( Value len ) { return lpad( (int) len.getNumber(), " " ); } public Value lpad( Value len, Value padstr ) { return lpad( (int) len.getNumber(), padstr.getString() ); } public Value lpad( int len ) { return lpad( len, " " ); } public Value lpad( int len, String padstr ) { if ( isNull() ) { setType( VALUE_TYPE_STRING ); } else { if ( getType() != VALUE_TYPE_STRING ) { // also lpad other types! setValue( getString() ); } if ( getString() != null ) { StringBuilder result = new StringBuilder( getString() ); int pad = len; int l = ( pad - result.length() ) / padstr.length() + 1; int i; for ( i = 0; i < l; i++ ) { result.insert( 0, padstr ); } // Maybe we added one or two too many! i = result.length(); while ( i > pad && pad > 0 ) { result.deleteCharAt( 0 ); i--; } setValue( result.toString() ); } else { setNull(); } } setLength( len ); return this; } // implement the LTRIM function public Value ltrim() { if ( isNull() ) { setType( VALUE_TYPE_STRING ); } else { if ( getString() != null ) { String s; if ( getType() == VALUE_TYPE_STRING ) { s = Const.ltrim( getString() ); } else { s = Const.ltrim( toString() ); } setValue( s ); } else { setNull(); } } return this; } // implement the MOD function, arguments in args[] public Value mod( Value arg ) throws KettleValueException { return mod( arg.getNumber() ); } public Value mod( BigDecimal arg ) throws KettleValueException { return mod( arg.doubleValue() ); } public Value mod( long arg ) throws KettleValueException { return mod( (double) arg ); } public Value mod( int arg ) throws KettleValueException { return mod( (double) arg ); } public Value mod( byte arg ) throws KettleValueException { return mod( (double) arg ); } public Value mod( double arg0 ) throws KettleValueException { if ( isNull() ) { return this; } if ( isNumeric() ) { double n1 = getNumber(); double n2 = arg0; setValue( n1 - ( n2 * Math.floor( n1 / n2 ) ) ); } else { throw new KettleValueException( "Function MOD only works with numeric data" ); } return this; } // implement the NVL function, arguments in args[] public Value nvl( Value alt ) { if ( isNull() ) { setValue( alt ); } return this; } // implement the POWER function, arguments in args[] public Value power( BigDecimal arg ) throws KettleValueException { return power( new Value( "tmp", arg ) ); } public Value power( double arg ) throws KettleValueException { return power( new Value( "tmp", arg ) ); } public Value power( Value v ) throws KettleValueException { if ( isNull() ) { return this; } else if ( isNumeric() ) { setValue( Math.pow( getNumber(), v.getNumber() ) ); } else { throw new KettleValueException( "Function POWER only works with numeric data" ); } return this; } // implement the REPLACE function, arguments in args[] public Value replace( Value repl, Value with ) { return replace( repl.getString(), with.getString() ); } public Value replace( String repl, String with ) { if ( isNull() ) { return this; } if ( getString() == null ) { setNull(); } else { setValue( Const.replace( getString(), repl, with ) ); } return this; } /** * Rounds off to the nearest integer. * <p> * See also: java.lang.Math.round() * * @return The rounded Number value. */ public Value round() throws KettleValueException { if ( isNull() ) { return this; } if ( isNumeric() ) { setValue( (double) Math.round( getNumber() ) ); } else { throw new KettleValueException( "Function ROUND only works with a number" ); } return this; } /** * Rounds the Number value to a certain number decimal places. * * @param decimalPlaces * @return The rounded Number Value * @throws KettleValueException * in case it's not a number (or other problem). */ public Value round( int decimalPlaces ) throws KettleValueException { if ( isNull() ) { return this; } if ( isNumeric() ) { if ( isBigNumber() ) { // Multiply by 10^decimalPlaces // For example 123.458343938437, Decimalplaces = 2 // BigDecimal bigDec = getBigNumber(); // System.out.println("ROUND decimalPlaces : "+decimalPlaces+", bigNumber = "+bigDec); bigDec = bigDec.setScale( decimalPlaces, BigDecimal.ROUND_HALF_EVEN ); // System.out.println("ROUND finished result : "+bigDec); setValue( bigDec ); } else { setValue( Const.round( getNumber(), decimalPlaces ) ); } } else { throw new KettleValueException( "Function ROUND only works with a number" ); } return this; } // implement the RPAD function, arguments in args[] public Value rpad( Value len ) { return rpad( (int) len.getNumber(), " " ); } public Value rpad( Value len, Value padstr ) { return rpad( (int) len.getNumber(), padstr.getString() ); } public Value rpad( int len ) { return rpad( len, " " ); } public Value rpad( int len, String padstr ) { if ( isNull() ) { setType( VALUE_TYPE_STRING ); } else { if ( getType() != VALUE_TYPE_STRING ) { // also rpad other types! setValue( getString() ); } if ( getString() != null ) { StringBuilder result = new StringBuilder( getString() ); int pad = len; int l = ( pad - result.length() ) / padstr.length() + 1; int i; for ( i = 0; i < l; i++ ) { result.append( padstr ); } // Maybe we added one or two too many! i = result.length(); while ( i > pad && pad > 0 ) { result.deleteCharAt( i - 1 ); i--; } setValue( result.toString() ); } else { setNull(); } } setLength( len ); return this; } // implement the RTRIM function, arguments in args[] public Value rtrim() { if ( isNull() ) { setType( VALUE_TYPE_STRING ); } else { String s; if ( getType() == VALUE_TYPE_STRING ) { s = Const.rtrim( getString() ); } else { s = Const.rtrim( toString() ); } setValue( s ); } return this; } // implement the SIGN function, arguments in args[] public Value sign() throws KettleValueException { if ( isNull() ) { return this; } if ( isNumber() ) { int cmp = getBigNumber().compareTo( new BigDecimal( 0L ) ); if ( cmp > 0 ) { value.setBigNumber( new BigDecimal( 1L ) ); } else if ( cmp < 0 ) { value.setBigNumber( new BigDecimal( -1L ) ); } else { value.setBigNumber( new BigDecimal( 0L ) ); } } else if ( isNumber() ) { if ( getNumber() > 0 ) { value.setNumber( 1.0 ); } else if ( getNumber() < 0 ) { value.setNumber( -1.0 ); } else { value.setNumber( 0.0 ); } } else if ( isInteger() ) { if ( getInteger() > 0 ) { value.setInteger( 1 ); } else if ( getInteger() < 0 ) { value.setInteger( -1 ); } else { value.setInteger( 0 ); } } else { throw new KettleValueException( "Function SIGN only works with a number" ); } return this; } // implement the SIN function, arguments in args[] public Value sin() throws KettleValueException { if ( isNull() ) { return this; } if ( isNumeric() ) { setValue( Math.sin( getNumber() ) ); } else { throw new KettleValueException( "Function SIN only works with a number" ); } return this; } // implement the SQRT function, arguments in args[] public Value sqrt() throws KettleValueException { if ( isNull() ) { return this; } if ( isNumeric() ) { setValue( Math.sqrt( getNumber() ) ); } else { throw new KettleValueException( "Function SQRT only works with a number" ); } return this; } // implement the SUBSTR function, arguments in args[] public Value substr( Value from, Value to ) { return substr( (int) from.getNumber(), (int) to.getNumber() ); } public Value substr( Value from ) { return substr( (int) from.getNumber(), -1 ); } public Value substr( int from ) { return substr( from, -1 ); } public Value substr( int from, int to ) { if ( isNull() ) { setType( VALUE_TYPE_STRING ); return this; } setValue( getString() ); if ( getString() != null ) { if ( to < 0 && from >= 0 ) { setValue( getString().substring( from ) ); } else if ( to >= 0 && from >= 0 ) { setValue( getString().substring( from, to ) ); } } else { setNull(); } if ( !isString() ) { setType( VALUE_TYPE_STRING ); } return this; } // implement the RIGHTSTR function, arguments in args[] public Value rightstr( Value len ) { return rightstr( (int) len.getNumber() ); } public Value rightstr( int len ) { if ( isNull() ) { setType( VALUE_TYPE_STRING ); return this; } setValue( getString() ); int tot_len = getString() != null ? getString().length() : 0; if ( tot_len > 0 ) { int totlen = getString().length(); int f = totlen - len; if ( f < 0 ) { f = 0; } setValue( getString().substring( f ) ); } else { setNull(); } if ( !isString() ) { setType( VALUE_TYPE_STRING ); } return this; } // implement the LEFTSTR function, arguments in args[] public Value leftstr( Value len ) { return leftstr( (int) len.getNumber() ); } public Value leftstr( int len ) { if ( isNull() ) { setType( VALUE_TYPE_STRING ); return this; } setValue( getString() ); int tot_len = getString() != null ? getString().length() : 0; if ( tot_len > 0 ) { int totlen = getString().length(); int f = totlen - len; if ( f > 0 ) { setValue( getString().substring( 0, len ) ); } } else { setNull(); } if ( !isString() ) { setType( VALUE_TYPE_STRING ); } return this; } public Value startsWith( Value string ) { return startsWith( string.getString() ); } public Value startsWith( String string ) { if ( isNull() ) { setType( VALUE_TYPE_BOOLEAN ); return this; } if ( string == null ) { setValue( false ); setNull(); return this; } setValue( getString().startsWith( string ) ); return this; } // implement the SYSDATE function, arguments in args[] public Value sysdate() { setValue( Calendar.getInstance().getTime() ); return this; } // implement the TAN function, arguments in args[] public Value tan() throws KettleValueException { if ( isNull() ) { return this; } if ( isNumeric() ) { setValue( Math.tan( getNumber() ) ); } else { throw new KettleValueException( "Function TAN only works on a number" ); } return this; } // implement the TO_CHAR function, arguments in args[] // number: NUM2STR( 123.456 ) : default format // number: NUM2STR( 123.456, '###,##0.000') : format // number: NUM2STR( 123.456, '###,##0.000', '.') : grouping // number: NUM2STR( 123.456, '###,##0.000', '.', ',') : decimal // number: NUM2STR( 123.456, '###,##0.000', '.', ',', '?') : currency public Value num2str() throws KettleValueException { return num2str( null, null, null, null ); } public Value num2str( String format ) throws KettleValueException { return num2str( format, null, null, null ); } public Value num2str( String format, String decimalSymbol ) throws KettleValueException { return num2str( format, decimalSymbol, null, null ); } public Value num2str( String format, String decimalSymbol, String groupingSymbol ) throws KettleValueException { return num2str( format, decimalSymbol, groupingSymbol, null ); } public Value num2str( String format, String decimalSymbol, String groupingSymbol, String currencySymbol ) throws KettleValueException { if ( isNull() ) { setType( VALUE_TYPE_STRING ); } else { // Number to String conversion... if ( getType() == VALUE_TYPE_NUMBER || getType() == VALUE_TYPE_INTEGER ) { NumberFormat nf = NumberFormat.getInstance(); DecimalFormat df = (DecimalFormat) nf; DecimalFormatSymbols dfs = new DecimalFormatSymbols(); if ( currencySymbol != null && currencySymbol.length() > 0 ) { dfs.setCurrencySymbol( currencySymbol ); } if ( groupingSymbol != null && groupingSymbol.length() > 0 ) { dfs.setGroupingSeparator( groupingSymbol.charAt( 0 ) ); } if ( decimalSymbol != null && decimalSymbol.length() > 0 ) { dfs.setDecimalSeparator( decimalSymbol.charAt( 0 ) ); } df.setDecimalFormatSymbols( dfs ); // in case of 4, 3 or 2 if ( format != null && format.length() > 0 ) { df.applyPattern( format ); } try { setValue( nf.format( getNumber() ) ); } catch ( Exception e ) { setType( VALUE_TYPE_STRING ); setNull(); throw new KettleValueException( "Couldn't convert Number to String " + e.toString() ); } } else { throw new KettleValueException( "Function NUM2STR only works on Numbers and Integers" ); } } return this; } // date: TO_CHAR( <date> , 'yyyy/mm/dd HH:mm:ss' public Value dat2str() throws KettleValueException { return dat2str( null, null ); } public Value dat2str( String arg0 ) throws KettleValueException { return dat2str( arg0, null ); } public Value dat2str( String arg0, String arg1 ) throws KettleValueException { if ( isNull() ) { setType( VALUE_TYPE_STRING ); } else { if ( getType() == VALUE_TYPE_DATE ) { SimpleDateFormat df = new SimpleDateFormat(); DateFormatSymbols dfs = new DateFormatSymbols(); if ( arg1 != null ) { dfs.setLocalPatternChars( arg1 ); } if ( arg0 != null ) { df.applyPattern( arg0 ); } try { setValue( df.format( getDate() ) ); } catch ( Exception e ) { setType( VALUE_TYPE_STRING ); setNull(); throw new KettleValueException( "TO_CHAR Couldn't convert Date to String " + e.toString() ); } } else { throw new KettleValueException( "Function DAT2STR only works on a date" ); } } return this; } // implement the TO_DATE function, arguments in args[] public Value num2dat() throws KettleValueException { if ( isNull() ) { setType( VALUE_TYPE_DATE ); } else { if ( isNumeric() ) { setValue( new Date( getInteger() ) ); setLength( -1, -1 ); } else { throw new KettleValueException( "Function NUM2DAT only works on a number" ); } } return this; } public Value str2dat( String arg0 ) throws KettleValueException { return str2dat( arg0, null ); } public Value str2dat( String arg0, String arg1 ) throws KettleValueException { if ( isNull() ) { setType( VALUE_TYPE_DATE ); } else { // System.out.println("Convert string ["+string+"] to date using pattern '"+arg0+"'"); SimpleDateFormat df = new SimpleDateFormat(); DateFormatSymbols dfs = new DateFormatSymbols(); if ( arg1 != null ) { dfs.setLocalPatternChars( arg1 ); } if ( arg0 != null ) { df.applyPattern( arg0 ); } try { value.setDate( df.parse( getString() ) ); setType( VALUE_TYPE_DATE ); setLength( -1, -1 ); } catch ( Exception e ) { setType( VALUE_TYPE_DATE ); setNull(); throw new KettleValueException( "TO_DATE Couldn't convert String to Date" + e.toString() ); } } return this; } // implement the TO_NUMBER function, arguments in args[] public Value str2num() throws KettleValueException { return str2num( null, null, null, null ); } public Value str2num( String pattern ) throws KettleValueException { return str2num( pattern, null, null, null ); } public Value str2num( String pattern, String decimal ) throws KettleValueException { return str2num( pattern, decimal, null, null ); } public Value str2num( String pattern, String decimal, String grouping ) throws KettleValueException { return str2num( pattern, decimal, grouping, null ); } public Value str2num( String pattern, String decimal, String grouping, String currency ) throws KettleValueException { // 0 : pattern // 1 : Decimal separator // 2 : Grouping separator // 3 : Currency symbol if ( isNull() ) { setType( VALUE_TYPE_STRING ); } else { if ( getType() == VALUE_TYPE_STRING ) { if ( getString() == null ) { setNull(); setValue( 0.0 ); } else { NumberFormat nf = NumberFormat.getInstance(); DecimalFormat df = (DecimalFormat) nf; DecimalFormatSymbols dfs = new DecimalFormatSymbols(); if ( !Utils.isEmpty( pattern ) ) { df.applyPattern( pattern ); } if ( !Utils.isEmpty( decimal ) ) { dfs.setDecimalSeparator( decimal.charAt( 0 ) ); } if ( !Utils.isEmpty( grouping ) ) { dfs.setGroupingSeparator( grouping.charAt( 0 ) ); } if ( !Utils.isEmpty( currency ) ) { dfs.setCurrencySymbol( currency ); } try { df.setDecimalFormatSymbols( dfs ); setValue( df.parse( getString() ).doubleValue() ); } catch ( Exception e ) { String message = "Couldn't convert string to number " + e.toString(); if ( !Utils.isEmpty( pattern ) ) { message += " pattern=" + pattern; } if ( !Utils.isEmpty( decimal ) ) { message += " decimal=" + decimal; } if ( !Utils.isEmpty( grouping ) ) { message += " grouping=" + grouping.charAt( 0 ); } if ( !Utils.isEmpty( currency ) ) { message += " currency=" + currency; } throw new KettleValueException( message ); } } } else { throw new KettleValueException( "Function STR2NUM works only on strings" ); } } return this; } public Value dat2num() throws KettleValueException { if ( isNull() ) { setType( VALUE_TYPE_INTEGER ); return this; } if ( getType() == VALUE_TYPE_DATE ) { if ( getString() == null ) { setNull(); setValue( 0L ); } else { setValue( getInteger() ); } } else { throw new KettleValueException( "Function DAT2NUM works only on dates" ); } return this; } /** * Performs a right and left trim of spaces in the string. If the value is not a string a conversion to String is * performed first. * * @return The trimmed string value. */ public Value trim() { if ( isNull() ) { setType( VALUE_TYPE_STRING ); return this; } String str = Const.trim( getString() ); setValue( str ); return this; } // implement the UPPER function, arguments in args[] public Value upper() { if ( isNull() ) { setType( VALUE_TYPE_STRING ); return this; } setValue( getString().toUpperCase() ); return this; } // implement the E function, arguments in args[] public Value e() { setValue( Math.E ); return this; } // implement the PI function, arguments in args[] public Value pi() { setValue( Math.PI ); return this; } // implement the DECODE function, arguments in args[] public Value v_decode( Value[] args ) throws KettleValueException { int i; boolean found; // Decode takes as input the first argument... // The next pair // Limit to 3, 5, 7, 9, ... arguments if ( args.length >= 3 && ( args.length % 2 ) == 1 ) { i = 0; found = false; while ( i < args.length - 1 && !found ) { if ( this.equals( args[i] ) ) { setValue( args[i + 1] ); found = true; } i += 2; } if ( !found ) { setValue( args[args.length - 1] ); } } else { // ERROR with nr of arguments throw new KettleValueException( "Function DECODE can't have " + args.length + " arguments!" ); } return this; } // implement the IF function, arguments in args[] // IF( <condition>, <then value>, <else value>) public Value v_if( Value[] args ) throws KettleValueException { if ( getType() == VALUE_TYPE_BOOLEAN ) { if ( args.length == 1 ) { if ( getBoolean() ) { setValue( args[0] ); } else { setNull(); } } else if ( args.length == 2 ) { if ( getBoolean() ) { setValue( args[0] ); } else { setValue( args[1] ); } } } else { throw new KettleValueException( "Function DECODE can't have " + args.length + " arguments!" ); } return this; } // implement the ADD_MONTHS function, one argument public Value add_months( int months ) throws KettleValueException { if ( getType() == VALUE_TYPE_DATE ) { if ( !isNull() && getDate() != null ) { Calendar cal = Calendar.getInstance(); cal.setTime( getDate() ); int year = cal.get( Calendar.YEAR ); int month = cal.get( Calendar.MONTH ); int day = cal.get( Calendar.DAY_OF_MONTH ); month += months; 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 ); } setValue( cal.getTime() ); } } else { throw new KettleValueException( "Function add_months only works on a date!" ); } return this; } /** * Add a number of days to a Date value. * * @param days * The number of days to add to the current date value * @return The resulting value * @throws KettleValueException */ public Value add_days( long days ) throws KettleValueException { if ( getType() == VALUE_TYPE_DATE ) { if ( !isNull() && getDate() != null ) { Calendar cal = Calendar.getInstance(); cal.setTime( getDate() ); cal.add( Calendar.DAY_OF_YEAR, (int) days ); setValue( cal.getTime() ); } } else { throw new KettleValueException( "Function add_days only works on a date!" ); } return this; } // implement the LAST_DAY function, arguments in args[] public Value last_day() throws KettleValueException { if ( getType() == VALUE_TYPE_DATE ) { Calendar cal = Calendar.getInstance(); cal.setTime( getDate() ); int last_day = cal.getActualMaximum( Calendar.DAY_OF_MONTH ); cal.set( Calendar.DAY_OF_MONTH, last_day ); setValue( cal.getTime() ); } else { throw new KettleValueException( "Function last_day only works on a date" ); } return this; } public Value first_day() throws KettleValueException { if ( getType() == VALUE_TYPE_DATE ) { Calendar cal = Calendar.getInstance(); cal.setTime( getDate() ); cal.set( Calendar.DAY_OF_MONTH, 1 ); setValue( cal.getTime() ); } else { throw new KettleValueException( "Function first_day only works on a date" ); } return this; } // implement the TRUNC function, version without arguments public Value trunc() throws KettleValueException { if ( isNull() ) { return this; // don't do anything, leave it at NULL! } if ( isInteger() ) { // Nothing return this; } if ( isBigNumber() ) { getBigNumber().setScale( 0, BigDecimal.ROUND_FLOOR ); } else if ( isNumber() ) { setValue( Math.floor( getNumber() ) ); } else if ( isDate() ) { Calendar cal = Calendar.getInstance(); cal.setTime( getDate() ); cal.set( Calendar.MILLISECOND, 0 ); cal.set( Calendar.SECOND, 0 ); cal.set( Calendar.MINUTE, 0 ); cal.set( Calendar.HOUR_OF_DAY, 0 ); setValue( cal.getTime() ); } else { throw new KettleValueException( "Function TRUNC only works on numbers and dates" ); } return this; } // implement the TRUNC function, arguments in args[] public Value trunc( double level ) throws KettleValueException { return trunc( (int) level ); } @SuppressWarnings( "fallthrough" ) public Value trunc( int level ) throws KettleValueException { if ( isNull() ) { return this; // don't do anything, leave it at NULL! } if ( isInteger() ) { return this; // nothing to do. } if ( isBigNumber() ) { getBigNumber().setScale( level, BigDecimal.ROUND_FLOOR ); } else if ( isNumber() ) { double pow = Math.pow( 10, level ); setValue( Math.floor( getNumber() * pow ) / pow ); } else if ( isDate() ) { Calendar cal = Calendar.getInstance(); cal.setTime( getDate() ); switch ( level ) { // MONTHS case 5: cal.set( Calendar.MONTH, 1 ); // DAYS case 4: cal.set( Calendar.DAY_OF_MONTH, 1 ); // HOURS case 3: cal.set( Calendar.HOUR_OF_DAY, 0 ); // MINUTES case 2: cal.set( Calendar.MINUTE, 0 ); // SECONDS case 1: cal.set( Calendar.SECOND, 0 ); // MILI-SECONDS case 0: cal.set( Calendar.MILLISECOND, 0 ); break; default: throw new KettleValueException( "Argument of TRUNC of date has to be between 0 and 5" ); } } else { throw new KettleValueException( "Function TRUNC only works with numbers and dates" ); } return this; } /** * Change a string into its hexadecimal representation. E.g. if Value contains string "a" afterwards it would contain * value "61". * * Note that transformations happen in groups of 2 hex characters, so the value of a characters is always in the range * 0-255. * * @return Value itself * @throws KettleValueException */ public Value byteToHexEncode() { final char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; setType( VALUE_TYPE_STRING ); if ( isNull() ) { return this; } String hex = getString(); // 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)" char[] s = hex.toCharArray(); StringBuilder hexString = new StringBuilder( 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 } setValue( hexString ); return this; } /** * 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 Value hexToByteDecode() throws KettleValueException { setType( VALUE_TYPE_STRING ); if ( isNull() ) { return this; } setValue( getString() ); String hexString = getString(); 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; } setValue( new String( chArray ) ); return this; } /** * 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 Value itself * @throws KettleValueException */ public Value charToHexEncode() { final char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; setType( VALUE_TYPE_STRING ); if ( isNull() ) { return this; } String hex = getString(); char[] s = hex.toCharArray(); StringBuilder hexString = new StringBuilder( 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 } setValue( hexString ); return this; } /** * 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 Value itself * @throws KettleValueException */ public Value hexToCharDecode() throws KettleValueException { setType( VALUE_TYPE_STRING ); if ( isNull() ) { return this; } setValue( getString() ); String hexString = getString(); 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++; } } setValue( new String( chArray ) ); return this; } /* * Some javascript extensions... */ public static final Value getInstance() { return new Value(); } public String getClassName() { return "Value"; } public void jsConstructor() { } public void jsConstructor( String name ) { setName( name ); } public void jsConstructor( String name, String value ) { setName( name ); setValue( value ); } /** * Produce the XML representation of this value. * * @return a String containing the XML to represent this Value. */ @Override public String getXML() { StringBuilder retval = new StringBuilder( 128 ); retval.append( "<" + XML_TAG + ">" ); retval.append( XMLHandler.addTagValue( "name", getName(), false ) ); retval.append( XMLHandler.addTagValue( "type", getTypeDesc(), false ) ); retval.append( XMLHandler.addTagValue( "text", toString( false ), false ) ); retval.append( XMLHandler.addTagValue( "length", getLength(), false ) ); retval.append( XMLHandler.addTagValue( "precision", getPrecision(), false ) ); retval.append( XMLHandler.addTagValue( "isnull", isNull(), false ) ); retval.append( "</" + XML_TAG + ">" ); return retval.toString(); } /** * Construct a new Value and read the data from XML * * @param valnode * The XML Node to read from. */ public Value( Node valnode ) { this(); loadXML( valnode ); } /** * Read the data for this Value from an XML Node * * @param valnode * The XML Node to read from * @return true if all went well, false if something went wrong. */ public boolean loadXML( Node valnode ) { try { String valname = XMLHandler.getTagValue( valnode, "name" ); int valtype = getType( XMLHandler.getTagValue( valnode, "type" ) ); String text = XMLHandler.getTagValue( valnode, "text" ); boolean isnull = "Y".equalsIgnoreCase( XMLHandler.getTagValue( valnode, "isnull" ) ); int len = Const.toInt( XMLHandler.getTagValue( valnode, "length" ), -1 ); int prec = Const.toInt( XMLHandler.getTagValue( valnode, "precision" ), -1 ); setName( valname ); setValue( text ); setLength( len, prec ); if ( valtype != VALUE_TYPE_STRING ) { trim(); convertString( valtype ); } if ( isnull ) { setNull(); } } catch ( Exception e ) { setNull(); return false; } return true; } /** * Convert this Value from type String to another type * * @param newtype * The Value type to convert to. */ public void convertString( int newtype ) throws KettleValueException { switch ( newtype ) { case VALUE_TYPE_STRING: break; case VALUE_TYPE_NUMBER: setValue( getNumber() ); break; case VALUE_TYPE_DATE: setValue( getDate() ); break; case VALUE_TYPE_BOOLEAN: setValue( getBoolean() ); break; case VALUE_TYPE_INTEGER: setValue( getInteger() ); break; case VALUE_TYPE_BIGNUMBER: setValue( getBigNumber() ); break; default: throw new KettleValueException( "Please specify the type to convert to from String type." ); } } public boolean equalValueType( Value v ) { return equalValueType( v, false ); } /** * Returns whether "types" of the values are exactly the same: type, name, length, precision. * * @param v * Value to compare type against. * * @return == true when types are the same == false when the types differ */ public boolean equalValueType( Value v, boolean checkTypeOnly ) { if ( v == null ) { return false; } if ( getType() != v.getType() ) { return false; } if ( !checkTypeOnly ) { if ( ( getName() == null && v.getName() != null ) || ( getName() != null && v.getName() == null ) || !( getName().equals( v.getName() ) ) ) { return false; } if ( getLength() != v.getLength() ) { return false; } if ( getPrecision() != v.getPrecision() ) { return false; } } return true; } public ValueInterface getValueInterface() { return value; } public void setValueInterface( ValueInterface valueInterface ) { this.value = valueInterface; } /** * Merges another Value. That means, that if the other Value has got the same name and is of the same type as this * Value, it's real field value is set as this this' value, if our value is <code>null</code> or empty * * @param other * The other value */ public void merge( Value other ) { // Prechecks: Not null (of course) and same name and same type if ( other == null || !getName().equals( other.getName() ) || getType() != other.getType() ) { return; } switch ( getType() ) { case VALUE_TYPE_BIGNUMBER: if ( getBigNumber() == null ) { setValue( other.getBigNumber() ); } break; case VALUE_TYPE_BINARY: if ( getBytes() == null || getBytes().length == 0 ) { if ( other.getBytes() != null && other.getBytes().length > 0 ) { setValue( other.getBytes() ); } } break; case VALUE_TYPE_BOOLEAN: // 'false' cannot be said to be 'empty' (could be set on purpose) so we better don't overwrite // with 'true'. break; case VALUE_TYPE_DATE: if ( getDate() == null ) { setValue( other.getDate() ); } break; case VALUE_TYPE_INTEGER: if ( getInteger() == 0l ) { setValue( other.getInteger() ); } break; case VALUE_TYPE_NUMBER: if ( getNumber() == 0.0 ) { setValue( other.getNumber() ); } break; case VALUE_TYPE_SERIALIZABLE: // Cannot transfer serializables break; case VALUE_TYPE_STRING: if ( Utils.isEmpty( getString() ) && !Utils.isEmpty( other.getString() ) ) { setValue( other.getString() ); } break; default: break; } } }