/*! ******************************************************************************
*
* 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.trans.steps.formula;
import java.math.BigDecimal;
import java.util.Date;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.exception.KettleValueException;
import org.pentaho.di.core.row.RowDataUtil;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.core.row.ValueMetaInterface;
import org.pentaho.di.core.row.value.ValueMetaFactory;
import org.pentaho.di.trans.Trans;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.trans.step.BaseStep;
import org.pentaho.di.trans.step.StepDataInterface;
import org.pentaho.di.trans.step.StepInterface;
import org.pentaho.di.trans.step.StepMeta;
import org.pentaho.di.trans.step.StepMetaInterface;
import org.pentaho.reporting.libraries.formula.LibFormulaErrorValue;
import org.pentaho.reporting.libraries.formula.parser.FormulaParser;
/**
* Calculate new field values using pre-defined functions.
*
* @author Matt
* @since 8-sep-2005
*/
public class Formula extends BaseStep implements StepInterface {
private FormulaMeta meta;
private FormulaData data;
public Formula( StepMeta stepMeta, StepDataInterface stepDataInterface, int copyNr, TransMeta transMeta,
Trans trans ) {
super( stepMeta, stepDataInterface, copyNr, transMeta, trans );
}
public boolean processRow( StepMetaInterface smi, StepDataInterface sdi ) throws KettleException {
meta = (FormulaMeta) smi;
data = (FormulaData) sdi;
Object[] r = getRow(); // get row, set busy!
if ( r == null ) { // no more input to be expected...
setOutputDone();
return false;
}
if ( first ) {
first = false;
data.outputRowMeta = getInputRowMeta().clone();
meta.getFields( data.outputRowMeta, getStepname(), null, null, this, repository, metaStore );
// Create the context
data.context = new RowForumulaContext( data.outputRowMeta );
data.parser = new FormulaParser();
// Calculate replace indexes...
//
data.replaceIndex = new int[meta.getFormula().length];
for ( int i = 0; i < meta.getFormula().length; i++ ) {
FormulaMetaFunction fn = meta.getFormula()[i];
if ( !Utils.isEmpty( fn.getReplaceField() ) ) {
data.replaceIndex[i] = getInputRowMeta().indexOfValue( fn.getReplaceField() );
if ( data.replaceIndex[i] < 0 ) {
throw new KettleException( "Unknown field specified to replace with a formula result: ["
+ fn.getReplaceField() + "]" );
}
} else {
data.replaceIndex[i] = -1;
}
}
}
if ( log.isRowLevel() ) {
logRowlevel( "Read row #" + getLinesRead() + " : " + r );
}
Object[] outputRowData = calcFields( getInputRowMeta(), r );
putRow( data.outputRowMeta, outputRowData ); // copy row to possible alternate rowset(s).
if ( log.isRowLevel() ) {
logRowlevel( "Wrote row #" + getLinesWritten() + " : " + r );
}
if ( checkFeedback( getLinesRead() ) ) {
logBasic( "Linenr " + getLinesRead() );
}
return true;
}
private Object[] calcFields( RowMetaInterface rowMeta, Object[] r ) throws KettleValueException {
try {
Object[] outputRowData = RowDataUtil.createResizedCopy( r, data.outputRowMeta.size() );
int tempIndex = rowMeta.size();
// Assign this tempRowData to the formula context
//
data.context.setRowData( outputRowData );
// Initialize parsers etc. Only do it once.
//
if ( data.formulas == null ) {
// Create a set of LValues to put the parsed results in...
data.formulas = new org.pentaho.reporting.libraries.formula.Formula[meta.getFormula().length];
for ( int i = 0; i < meta.getFormula().length; i++ ) {
FormulaMetaFunction fn = meta.getFormula()[i];
if ( !Utils.isEmpty( fn.getFieldName() ) ) {
data.formulas[i] = data.createFormula( meta.getFormula()[i].getFormula() );
} else {
throw new KettleException( "Unable to find field name for formula ["
+ Const.NVL( fn.getFormula(), "" ) + "]" );
}
}
}
for ( int i = 0; i < meta.getFormula().length; i++ ) {
FormulaMetaFunction fn = meta.getFormula()[i];
if ( !Utils.isEmpty( fn.getFieldName() ) ) {
if ( data.formulas[i] == null ) {
data.formulas[i] = data.createFormula( meta.getFormula()[i].getFormula() );
}
// this is main part of all this step: calculate formula
Object formulaResult = data.formulas[i].evaluate();
if ( formulaResult instanceof LibFormulaErrorValue ) {
// inspect why it is happens to get clear error message.
throw new KettleException( "Error calculate formula. Formula "
+ fn.getFormula() + " output field: " + fn.getFieldName() + ", error is: " + formulaResult.toString() );
}
// Calculate the return type on the first row...
// for most cases we can try to convert data on a fly.
if ( data.returnType[i] < 0 ) {
if ( formulaResult instanceof String ) {
data.returnType[i] = FormulaData.RETURN_TYPE_STRING;
fn.setNeedDataConversion( fn.getValueType() != ValueMetaInterface.TYPE_STRING );
} else if ( formulaResult instanceof Integer ) {
data.returnType[i] = FormulaData.RETURN_TYPE_INTEGER;
fn.setNeedDataConversion( fn.getValueType() != ValueMetaInterface.TYPE_INTEGER );
} else if ( formulaResult instanceof Long ) {
data.returnType[i] = FormulaData.RETURN_TYPE_LONG;
fn.setNeedDataConversion( fn.getValueType() != ValueMetaInterface.TYPE_INTEGER );
} else if ( formulaResult instanceof Date ) {
data.returnType[i] = FormulaData.RETURN_TYPE_DATE;
fn.setNeedDataConversion( fn.getValueType() != ValueMetaInterface.TYPE_DATE );
} else if ( formulaResult instanceof BigDecimal ) {
data.returnType[i] = FormulaData.RETURN_TYPE_BIGDECIMAL;
fn.setNeedDataConversion( fn.getValueType() != ValueMetaInterface.TYPE_BIGNUMBER );
} else if ( formulaResult instanceof Number ) {
data.returnType[i] = FormulaData.RETURN_TYPE_NUMBER;
fn.setNeedDataConversion( fn.getValueType() != ValueMetaInterface.TYPE_NUMBER );
// this types we will not make attempt to auto-convert
} else if ( formulaResult instanceof byte[] ) {
data.returnType[i] = FormulaData.RETURN_TYPE_BYTE_ARRAY;
if ( fn.getValueType() != ValueMetaInterface.TYPE_BINARY ) {
throw new KettleValueException( "Please specify a Binary type for field ["
+ fn.getFieldName() + "] as a result of formula [" + fn.getFormula() + "]" );
}
} else if ( formulaResult instanceof Boolean ) {
data.returnType[i] = FormulaData.RETURN_TYPE_BOOLEAN;
if ( fn.getValueType() != ValueMetaInterface.TYPE_BOOLEAN ) {
throw new KettleValueException( "Please specify a Boolean type for field ["
+ fn.getFieldName() + "] as a result of formula [" + fn.getFormula() + "]" );
}
} else {
data.returnType[i] = FormulaData.RETURN_TYPE_STRING;
fn.setNeedDataConversion( fn.getValueType() != ValueMetaInterface.TYPE_STRING );
}
}
int realIndex = ( data.replaceIndex[i] < 0 ) ? tempIndex++ : data.replaceIndex[i];
outputRowData[realIndex] = getReturnValue( formulaResult, data.returnType[i], realIndex, fn );
}
}
return outputRowData;
} catch ( Throwable e ) {
throw new KettleValueException( e );
}
}
protected Object getReturnValue( Object formulaResult, int returnType, int realIndex, FormulaMetaFunction fn )
throws KettleException {
if ( formulaResult == null ) {
return null;
}
Object value = null;
switch ( returnType ) {
case FormulaData.RETURN_TYPE_STRING:
if ( fn.isNeedDataConversion() ) {
value = convertDataToTargetValueMeta( realIndex, formulaResult );
} else {
value = formulaResult.toString();
}
break;
case FormulaData.RETURN_TYPE_NUMBER:
if ( fn.isNeedDataConversion() ) {
value = convertDataToTargetValueMeta( realIndex, formulaResult );
} else {
value = new Double( ( (Number) formulaResult ).doubleValue() );
}
break;
case FormulaData.RETURN_TYPE_INTEGER:
if ( fn.isNeedDataConversion() ) {
value = convertDataToTargetValueMeta( realIndex, formulaResult );
} else {
value = new Long( ( (Integer) formulaResult ).intValue() );
}
break;
case FormulaData.RETURN_TYPE_LONG:
if ( fn.isNeedDataConversion() ) {
value = convertDataToTargetValueMeta( realIndex, formulaResult );
} else {
value = formulaResult;
}
break;
case FormulaData.RETURN_TYPE_DATE:
if ( fn.isNeedDataConversion() ) {
value = convertDataToTargetValueMeta( realIndex, formulaResult );
} else {
value = formulaResult;
}
break;
case FormulaData.RETURN_TYPE_BIGDECIMAL:
if ( fn.isNeedDataConversion() ) {
value = convertDataToTargetValueMeta( realIndex, formulaResult );
} else {
value = formulaResult;
}
break;
case FormulaData.RETURN_TYPE_BYTE_ARRAY:
value = formulaResult;
break;
case FormulaData.RETURN_TYPE_BOOLEAN:
value = formulaResult;
break;
} //if none case is caught - null is returned.
return value;
}
private Object convertDataToTargetValueMeta( int i, Object formulaResult ) throws KettleException {
if ( formulaResult == null ) {
return formulaResult;
}
ValueMetaInterface target = data.outputRowMeta.getValueMeta( i );
ValueMetaInterface actual = ValueMetaFactory.guessValueMetaInterface( formulaResult );
Object value = target.convertData( actual, formulaResult );
return value;
}
public boolean init( StepMetaInterface smi, StepDataInterface sdi ) {
meta = (FormulaMeta) smi;
data = (FormulaData) sdi;
if ( super.init( smi, sdi ) ) {
// Add init code here.
// Return data type discovery is expensive, let's discover them one time only.
//
data.returnType = new int[meta.getFormula().length];
for ( int i = 0; i < meta.getFormula().length; i++ ) {
data.returnType[i] = -1;
}
return true;
}
return false;
}
}