/*******************************************************************************
 * Copyright (c) 2004 Actuate Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *  Actuate Corporation  - initial API and implementation
 *******************************************************************************/
package org.eclipse.birt.data.engine.script;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.eclipse.birt.core.data.DataTypeUtil;
import org.eclipse.birt.core.exception.BirtException;
import org.eclipse.birt.core.script.ScriptContext;
import org.eclipse.birt.data.engine.api.IBaseExpression;
import org.eclipse.birt.data.engine.api.IConditionalExpression;
import org.eclipse.birt.data.engine.api.IExpressionCollection;
import org.eclipse.birt.data.engine.api.IScriptExpression;
import org.eclipse.birt.data.engine.api.querydefn.ConditionalExpression;
import org.eclipse.birt.data.engine.api.querydefn.ScriptExpression;
import org.eclipse.birt.data.engine.core.DataException;
import org.eclipse.birt.data.engine.expression.CompiledExpression;
import org.eclipse.birt.data.engine.i18n.ResourceConstants;
import org.eclipse.birt.data.engine.impl.LogUtil;
import org.mozilla.javascript.Scriptable;


/**
 * Static utility methods to help evaluating Javascript expressions 
 */
public class ScriptEvalUtil
{	
	private static Logger logger = Logger.getLogger( ScriptEvalUtil.class.getName( ) );
	
	/**
	 * No instance
	 */
	private ScriptEvalUtil( )
	{
	}
	
	/**
	 * @param exprText
	 * @param value
	 * @return an instance of ExprTextAndValue
	 */
	public static ExprTextAndValue newExprInfo( String exprText, Object value )
	{
		return ExprTextAndValue.newInstance( exprText, value );
	}
	
	/**
	 * Evaluates a conditional expression. A conditional expression comprises of
	 * a Javascript expression, an operator, and up to 2 operands (which are
	 * Javascript expressions themselves).<br>
	 * Both op1 and op2 will be encapsulated to ExprTextAndValue type to show
	 * specific message in case anything goes wrong, they are assumed not to be
	 * null as well.
	 * <p>
	 * The basic rule for comparison: obj will always be considered as the
	 * default data type,i.e. obj, op1 and op2 will be formatted to the superset
	 * of obj (or Double if obj is numeric)on the condition they are comparable.<br>
	 * e.g.<br>
	 * obj: Integer=>obj, op1 and op2 will be formatted to Double.<br>
	 * obj: Timestamp=>obj, op1 and op2 will be formatted to Date.<br>
	 * obj: Boolean=>obj and op1 will be formatted to Boolean.<br>
	 * obj: String=>obj, op1 and op2 will remain the same
	 * 
	 * @param obj
	 * @param operator
	 * @param Op1
	 * @param Op2
	 * @return
	 * @throws DataException
	 */
	public static Object evalConditionalExpr( Object obj, int operator,
			Object Op1, Object Op2 ) throws DataException
	{
		return evalConditionalExpr( obj, operator, new Object[]{
				Op1, Op2
		} );
	}
	
	/**
	 * 
	 * @param obj
	 * @param operator
	 * @param op1
	 * @param op2
	 * @return A Boolean result
	 * @throws DataException
	 */
	public static Object evalConditionalExpr( Object obj,
			int operator, Object[] ops )
			throws DataException
	{
		ExprTextAndValue[] opTextAndValue = new ExprTextAndValue[ops.length]; 
		for( int i=0; i< ops.length; i++ )
		{
			opTextAndValue[i] = createExprTextAndValueInstance( ops[i] );
		}
		
		Object resultObject = obj;
		Object[] resultOp = new Object[ops.length];
		for( int i=0; i< ops.length; i++ )
		{
			resultOp[i] = opTextAndValue[i].value;
		}

		Object[] obArray = MiscUtil.isComparable( obj, operator, opTextAndValue );
		if ( obArray != null )
		{
			resultObject = obArray[0];
			for ( int i = 1; i < obArray.length; i++ )
			{
				resultOp[i - 1] = obArray[i];
			}
		}
		
		if ( logger.isLoggable( Level.FINER ) )
		{
			String logStr ="";
			for ( int i = 0; i < ops.length; i++ )
			{
				logStr += resultOp[i] == null
						? null
						: ( ", resultOp" + i + "=" + LogUtil.toString( resultOp[i] ) );
			}
			logger.entering( ScriptEvalUtil.class.getName( ),
					"evalConditionalExpr",
					"evalConditionalExpr() resultObject=" +
							LogUtil.toString( resultObject ) + ", operator=" +
							operator + logStr );
		}
		boolean result = false;
		switch ( operator )
		{
			case IConditionalExpression.OP_EQ :
			    result = compare( resultObject, resultOp[0] ) == 0;
				break;
			case IConditionalExpression.OP_NE :
			    result = compare( resultObject, resultOp[0] ) != 0;
				break;
			case IConditionalExpression.OP_LT :
				result = compare( resultObject, resultOp[0] ) < 0;
				break;
			case IConditionalExpression.OP_LE :
				result = compare( resultObject, resultOp[0] ) <= 0;
				break;
			case IConditionalExpression.OP_GE :
				result = compare( resultObject, resultOp[0] ) >= 0;
				break;
			case IConditionalExpression.OP_GT :
				result = compare( resultObject, resultOp[0] ) > 0;
				break;
			case IConditionalExpression.OP_BETWEEN :
				result = between( resultObject, resultOp[0], resultOp[1] );
				break;
			case IConditionalExpression.OP_NOT_BETWEEN :
				result = !( between( resultObject, resultOp[0], resultOp[1] ) );
				break;
			case IConditionalExpression.OP_NULL :
				result = resultObject == null;
				break;
			case IConditionalExpression.OP_NOT_NULL :
				result = resultObject != null;
				break;
			case IConditionalExpression.OP_TRUE :
				result = isTrueOrFalse( resultObject, Boolean.TRUE );
				break;
			case IConditionalExpression.OP_FALSE :
				result = isTrueOrFalse( resultObject, Boolean.FALSE );
				break;
			case IConditionalExpression.OP_LIKE :
				result = like( resultObject, resultOp[0] );
				break;
			case IConditionalExpression.OP_NOT_LIKE :
				result = !like( resultObject, resultOp[0] );
				break;
				
			case IConditionalExpression.OP_TOP_N :
			case IConditionalExpression.OP_BOTTOM_N :
			case IConditionalExpression.OP_TOP_PERCENT :
			case IConditionalExpression.OP_BOTTOM_PERCENT :
				// Top/Bottom expressions are only available in filters for now; direct evaluation is not supported
				throw new DataException(
						ResourceConstants.UNSUPPORTTED_COND_OPERATOR, "Top/Bottom(N) outside of row filters" );
				
		/*
		 * case IConditionalExpression.OP_ANY : throw new DataException(
		 * ResourceConstants.UNSUPPORTTED_COND_OPERATOR, "ANY" );
		 */
			case IConditionalExpression.OP_MATCH :
				result = match( resultObject, resultOp[0] );
				break;
			case IConditionalExpression.OP_NOT_MATCH :
				result = !match( resultObject, resultOp[0] );
				break;
			case IConditionalExpression.OP_IN :
				result = in( resultObject, resultOp );
				break;
			case IConditionalExpression.OP_NOT_IN :
				result = !in( resultObject, resultOp);
				break;
			default :
				throw new DataException(
						ResourceConstants.UNSUPPORTTED_COND_OPERATOR, new Integer(operator) );
		}
		
		logger.exiting( ScriptEvalUtil.class.getName( ),
				"evalConditionalExpr",
				new Boolean( result ) );
		return new Boolean( result );
	}

	/**
	 * @param o1
	 * @return
	 */
	private static ExprTextAndValue createExprTextAndValueInstance( Object o )
	{
		ExprTextAndValue op;
		if(! (o instanceof ExprTextAndValue ))
			op = ExprTextAndValue.newInstance( "", o );
		else
			op = (ExprTextAndValue)o;
		return op;
	}

	/**
	 * Compare two value according to given comparator.
	 * @param obj1
	 * @param obj2
	 * @param comp
	 * @return
	 * @throws DataException
	 */
	public static int compare( Object obj1, Object obj2, Comparator strComp )
			throws DataException
	{
		if ( obj1 == null || obj2 == null )
		{
			// all non-null values are greater than null value
			if ( obj1 == null && obj2 != null )
				return -1;
			else if ( obj1 != null && obj2 == null )
				return 1;
			else
				return 0;
		}

		try
		{
			if ( MiscUtil.isSameType( obj1, obj2 ) )
			{
				if ( obj1 instanceof String )
				{
					return compareAsString( obj1, obj2, strComp );
				}
				if ( obj1 instanceof Boolean )
				{
					if ( obj1.equals( obj2 ) )
						return 0;

					Boolean bool = (Boolean) obj1;
					if ( bool.equals( Boolean.TRUE ) )
						return 1;
					else
						return -1;
				}
				else if ( obj1 instanceof Comparable )
				{
					return ( (Comparable) obj1 ).compareTo( obj2 );
				}
				// most judgements should end here
				else
				{
					return compareAsString( obj1, obj2, strComp );
				}
			}
			else if ( MiscUtil.isBigDecimal( obj1 )
					|| MiscUtil.isBigDecimal( obj2 ) )
			{
				BigDecimal a = DataTypeUtil.toBigDecimal( obj1 );
				BigDecimal b = DataTypeUtil.toBigDecimal( obj2 );
				return a.compareTo( b );
			}
			else if ( MiscUtil.isNumericOrString( obj1 )
					&& MiscUtil.isNumericOrString( obj2 ) )
			{
				try
				{
					return DataTypeUtil.toDouble( obj1 )
							.compareTo( DataTypeUtil.toDouble( obj2 ) );
				}
				catch ( Exception e )
				{
					return compareAsString( obj1, obj2, strComp );
				}
			}
			else if ( MiscUtil.isDateOrString( obj1 )
					&& MiscUtil.isDateOrString( obj2 ) )
			{
				try
				{
					return DataTypeUtil.toDate( obj1 )
							.compareTo( DataTypeUtil.toDate( obj2 ) );
				}
				catch ( Exception e )
				{
					return compareAsString( obj1, obj2, strComp );
				}
			}
			else if ( MiscUtil.isBooleanOrString( obj1 )
					&& MiscUtil.isBooleanOrString( obj2 ) )
			{
				try
				{
					boolean b1 = DataTypeUtil.toBoolean( obj1 ).booleanValue( );
					boolean b2 = DataTypeUtil.toBoolean( obj2 ).booleanValue( );
					if ( b1 == b2 )
					{
						return 0;
					}
					else if ( b1 == false && b2 == true )
					{
						return -1;
					}
					else
					{
						return 1;
					}
				}
				catch ( Exception e )
				{
					return compareAsString( obj1, obj2, strComp );
				}
			}
			else
				throw new DataException( ResourceConstants.INVALID_TYPE_IN_EXPR );
		}
		catch ( BirtException e )
		{
			throw new DataException( ResourceConstants.DATATYPEUTIL_ERROR, e );
		}

	}

	private static int compareAsString( Object obj1, Object obj2,
			Comparator comp ) throws BirtException
	{
		return comp == null ? DataTypeUtil.toString( obj1 )
				.compareTo( DataTypeUtil.toString( obj2 ) )
				: comp.compare( DataTypeUtil.toString( obj1 ),
						DataTypeUtil.toString( obj2 ) );
	}
	
	/**
	 * Most objects should already be formatted to the same type by method
	 * formatToComparable at this point if neither of them is null. This method
	 * will therefore be terminated pretty soon except for calling from method
	 * between with weird parameters like obj:String, op1:Double and op2:Date.
	 * 
	 * @param obj1
	 * @param obj2
	 * @return -1,0 and 1 standing for <,= and > respectively
	 * @throws DataException
	 */
	public static int compare( Object obj1, Object obj2 ) throws DataException
	{
		return compare( obj1, obj2, null );
	}
	
	/**
	 * @param resultObject
	 * @param resultOp1
	 * @param resultOp2
	 * @return true if resultObject is between resultOp1 and resultOp2, false otherwise
	 * @throws DataException
	 */
	private static boolean between( Object resultObject, Object resultOp1,
			Object resultOp2 ) throws DataException
	{
		Object min, max;
		if ( compare( resultOp1, resultOp2 ) <= 0 )
		{
			min = resultOp1;
			max = resultOp2;
		}
		else
		{
			min = resultOp2;
			max = resultOp1;
		}
		return compare( resultObject, min ) >= 0
				&& compare( resultObject, max ) <= 0;
	}
	
	/**
	 * @param obj
	 * @param bln
	 * @return true if obj equals to bln, false otherwise
	 */
	private static boolean isTrueOrFalse( Object obj, Boolean bln )
	{
		if ( obj == null )
			return false;
		try
		{
			return DataTypeUtil.toBoolean( obj ).equals( bln );
		}
		catch ( BirtException e )
		{
			return false;
		}
	}
	// Pattern to determine if a Match operation uses Javascript regexp syntax
	private static Pattern s_JSReExprPattern;
	
	// Gets a matcher to determine if a match pattern string is of JavaScript syntax
	// The pattern matches string like "/regexpr/gmi", which is used in JavaScript to construct a RegExp object
	private static Matcher getJSReExprPatternMatcher( String patternStr )
	{
		if ( s_JSReExprPattern == null )
			s_JSReExprPattern = Pattern.compile("^/(.*)/([a-zA-Z]*)$");
		return s_JSReExprPattern.matcher( patternStr );
	}
	
	private static boolean match( Object source, Object pattern ) throws DataException
	{
		String sourceStr = (source == null)? "": source.toString();
		String patternStr = ( pattern == null )? "" : pattern.toString();

		// Pattern can be one of the following:
		// (1)Java regular expression pattern
		// (2)JavaScript RegExp construction syntax: "/RegExpr/[flags]", where flags 
		// can be a combination of 'g', 'm', 'i'
		Matcher jsReExprMatcher = getJSReExprPatternMatcher( patternStr ); 
		int flags = 0;
		if ( jsReExprMatcher.matches() )
		{
			// This is a Javascript syntax
			// Get the flags; we only expect "m", "i", "g"
			String flagStr = patternStr.substring( jsReExprMatcher.start(2), 
					jsReExprMatcher.end(2) );
			for ( int i = 0; i < flagStr.length(); i++)
			{
				switch ( flagStr.charAt(i) )
				{
					case 'm': flags |= Pattern.MULTILINE; break;
					case 'i': flags |= Pattern.CASE_INSENSITIVE; break;
					case 'g': break;			// this flag has no effect
						
					default:
						throw new DataException( ResourceConstants.MATCH_ERROR, patternStr );
				}
			}
			patternStr = patternStr.substring( jsReExprMatcher.start(1), 
					jsReExprMatcher.end(1) );
		}
		
		try
		{
			Matcher m = Pattern.compile( patternStr, flags ).matcher( sourceStr);
			return m.find(); 
		}
		catch ( PatternSyntaxException e )
		{
			throw new DataException( ResourceConstants.MATCH_ERROR, e, patternStr );
		}
	}

	/**
	 * @return true if obj1 matches the given pattern, false otherwise
	 * @throws DataException
	 */
	private static boolean like( Object source, Object pattern ) throws DataException
	{
		String sourceStr = (source == null)? "": source.toString();
		String patternStr = ( pattern == null )? "" : pattern.toString();
	
		// As per Bugzilla 115940, LIKE operator's pattern syntax is SQL-like: it
		// recognizes '_' and '%'. Backslash '\' escapes the next character.
		
		// Construct a Java RegExp pattern based on input. We need to translate 
		// unescaped '%' to '.*', and '_' to '.'
		// Also need to escape any RegExp metacharacter in the source pattern.
		
		final String reservedChars = "([{^$|)?*+.";
		int patternLen = patternStr.length();
		StringBuffer buffer = new StringBuffer( patternLen * 2 );
		
		for ( int i = 0; i < patternLen; i++)
		{
			char c = patternStr.charAt(i);
			if ( c == '\\' )
			{
				// Escape char; copy next character to new pattern if 
				// it is '\', '%' or '_'
				++i;
				if ( i < patternLen )
				{
					c = patternStr.charAt( i );
					if ( c == '%' || c == '_' )
						buffer.append( c );
					else if ( c == '\\' )
						buffer.append( "\\\\");		// Need to escape \
				}
				else
				{
					buffer.append( "\\\\" );  	// Leave last \ and escape it
				}
			}
			else if ( c == '%')
			{
				buffer.append(".*");
			}
			else if ( c == '_')
			{
				buffer.append(".");
			}
			else
			{
				// Copy this char to target, escape if it is a metacharacter
				if ( reservedChars.indexOf(c) >= 0 )
				{
					buffer.append('\\');
				}
				buffer.append(c);
			}
		}
		
		try
		{
			String newPatternStr = buffer.toString();
			Pattern p = Pattern.compile( newPatternStr );
			Matcher m = p.matcher( sourceStr.toString( ) );
			return m.matches( );
		}
		catch ( PatternSyntaxException e )
		{
			throw new DataException( ResourceConstants.MATCH_ERROR,  e, pattern );
		}
	}
	
	/**
	 * 
	 * @param resultObj
	 * @return
	 * @throws DataException
	 */
	private static boolean in( Object target, Object[] resultObj )
			throws DataException
	{
		if ( resultObj == null )
			return false;
		for ( int i = 0; i < resultObj.length; i++ )
		{
			if ( compare( target, resultObj[i] ) == 0 )
				return true;
		}
		return false;
	}

	/**
	 * Evaluates a IJSExpression or IConditionalExpression
	 * 	
	 * @param expr
	 * @param cx
	 * @param scope
	 * @param source
	 * @param lineNo
	 * @return 
	 * @throws DataException
	 */
	public static Object evalExpr( IBaseExpression expr, ScriptContext cx, Scriptable scope, 
				String source, int lineNo )
		throws DataException
	{
		if ( logger.isLoggable( Level.FINER ) )
			logger.entering( ScriptEvalUtil.class.getName( ),
				"evalExpr",
				"evalExpr() expr="
						+ LogUtil.toString( expr ) + ", source=" + source
						+ ", lineNo=" + lineNo );
		Object result;
		if ( expr == null )
		{
			result = null;
		}
		else if ( expr instanceof IConditionalExpression )
		{
			// If this is a prepared top(n)/bottom(n) expr, use its evaluator
			Object handle = expr.getHandle( );
			if ( handle instanceof NEvaluator )
			{
				result = Boolean.valueOf( ( (NEvaluator) handle ).evaluate( cx,
						scope ) );
			}
			else
			{
				ConditionalExpression conditionalExpr = (ConditionalExpression) expr;
				Object expression = evalExpr( conditionalExpr.getExpression( ),
						cx,
						scope,
						source,
						lineNo );
				if ( conditionalExpr.getOperand1( ) instanceof IExpressionCollection )
				{
					IExpressionCollection combinedExpr = (IExpressionCollection) ( (IConditionalExpression) expr ).getOperand1( );
					Object[] exprs = combinedExpr.getExpressions( ).toArray( );
					
					Object[] opValues = new Object[exprs.length];
					
					for ( int i = 0; i < opValues.length; i++ )
					{
						opValues[i] = evalExpr( (IBaseExpression)exprs[i],
								cx,
								scope,
								source,
								lineNo );
					}
					result = evalConditionalExpr( expression,
							conditionalExpr.getOperator( ),
							MiscUtil.flatternMultipleValues( opValues ) );
				}
				else
				{

					Object Op1 = evalExpr( MiscUtil.constructValidScriptExpression( (IScriptExpression) conditionalExpr.getOperand1( ) ),
							cx,
							scope,
							source,
							lineNo );
					Object Op2 = evalExpr( MiscUtil.constructValidScriptExpression( (IScriptExpression) conditionalExpr.getOperand2( ) ),
							cx,
							scope,
							source,
							lineNo );
					result = evalConditionalExpr( expression,
							conditionalExpr.getOperator( ),
							new Object[]{
									Op1, Op2
							} );
				}
			}
		}
		else
		{
			IScriptExpression jsExpr = (IScriptExpression) expr;
			if ( jsExpr.getText( ) != null && jsExpr.getHandle( ) != null )
			{
				result = ((CompiledExpression) jsExpr.getHandle( ) ).evaluate( cx, scope );
			}
			else
			{
				result = evaluateJSAsExpr( cx,
						scope,
						jsExpr.getText( ),
						source,
						lineNo );
			}
		}
		
		if ( logger.isLoggable( Level.FINER ) )
			logger.exiting( ScriptEvalUtil.class.getName( ),
					"evalExpr",
					result );
		return result;
	}	
	
	/**
	 * Evaluates a ROM script and converts the result type into one accepted by
	 * BIRT: Double (for all numeric types), java.util.Date, String, Boolean.
	 * Converts Javascript exception and script runtime exceptions to
	 * DataException
	 * 
	 * @param cx
	 * @param scope
	 * @param scriptText
	 * @param source
	 * @param lineNo
	 * @return
	 * @throws DataException
	 */
	public static Object evaluateJSAsExpr(ScriptContext cx, Scriptable scope,
			String scriptText, String source, int lineNo)
			throws DataException 
	{
		if ( logger.isLoggable( Level.FINER ) )
			logger.entering( ScriptEvalUtil.class.getName( ),
				"evaluateJSExpr",
				"evaluateJSExpr() scriptText=" + scriptText 
				+ ", source=" + source 
				+ ", lineNo=" + lineNo);
		
		Object result;
		try
		{
			result = cx.eval( scriptText, scope );
		}
		catch ( BirtException e )
		{
			throw DataException.wrap( e );
		}
		return result;
	}
	
	
	/**
	 * Wrap the text and value of the operand
	 * 
	 */
	public static class ExprTextAndValue
	{
		String exprText;
		Object value;

		/**
		 * 
		 * @param exprText
		 * @param value
		 * @return
		 */
		public static ExprTextAndValue newInstance( String exprText,
				Object value )
		{
			return new ExprTextAndValue( exprText, value );
		}

		/**
		 * 
		 * @param exprText
		 * @param value
		 */
		public ExprTextAndValue( String exprText, Object value )
		{
			this.exprText = exprText;
			this.value = value;
		}
	}	
	
	/**
	 * Utility for miscellaneous use
	 * 
	 */
	private static class MiscUtil
	{
		/**
		 * 
		 * @param resultExpr
		 * @param resultOp1
		 * @return
		 */
		private static boolean isSameType( Object resultExpr, Object resultOp1 )
		{
			return resultExpr.getClass( ).equals( resultOp1.getClass( ) );
		}
		
		/**
		 * 
		 * @param result
		 * @return
		 */
		private static boolean isNumericOrString( Object result )
		{
			return ( result instanceof Number ) || ( result instanceof String );
		}
		/**
		 * 
		 * @param result
		 * @return
		 */
		private static boolean isBigDecimal( Object result )
		{
			return result instanceof BigDecimal;
		}

		/**
		 * 
		 * @param result
		 * @return
		 */
		private static boolean isDateOrString( Object result )
		{
			return ( result instanceof Date ) || ( result instanceof String );
		}
		
		/**
		 * 
		 * @param result
		 * @return
		 */
		private static boolean isBooleanOrString( Object result )
		{
			return ( result instanceof Boolean ) || ( result instanceof String );
		}
		
		/**
		 * 
		 * @param obj
		 * @param operator
		 * @param operands
		 * @return
		 */
		private static Object[] isComparable( Object obj, int operator,
				ExprTextAndValue[] operands )
		{
			if ( needFormat( obj, operator, operands ) )
				return formatToComparable( obj, operands );
			return null;
		}

		/**
		 * 
		 * @param obj
		 * @param operator
		 * @param ops
		 * @return
		 */
		private static boolean needFormat( Object obj, int operator,
				ExprTextAndValue[] ops )
		{
			if ( operator < IConditionalExpression.OP_EQ
					|| ( operator > IConditionalExpression.OP_NOT_BETWEEN && operator < IConditionalExpression.OP_IN )
					|| obj == null || ops.length == 0 || ops[0].value == null )
				return false;
			// op2.value can not be null either if it's a between method
			else if ( ( operator == IConditionalExpression.OP_BETWEEN || operator == IConditionalExpression.OP_NOT_BETWEEN ) &&
					ops.length < 2 )
				return false;

			return true;
		}
		
		/**
		 * To ease the methods compare and between. Exception with specific
		 * explanation will be thrown if anything goes wrong.
		 * 
		 * @param obj
		 * @param operands
		 * @return
		 */
		private static Object[] formatToComparable( Object obj,
				ExprTextAndValue[] operands )
		{
			Object[] obArray = new Object[operands.length + 1];
			obArray[0] = obj;
			for ( int i = 0; i < operands.length; i++ )
			{
				obArray[i + 1] = operands[i].value;
			}
			boolean isSameType = true;
			
			// obj will always be considered as the default data type
			// skip if op2.value!=null but is not same type as obj
			if ( isSameType( obj, obArray[1] ) )
			{
				for ( int i = 1; i < operands.length; i++ )
				{
					if ( obArray[i + 1] != null &&
							!isSameType( obj, obArray[i + 1] ) )
					{
						isSameType = false;
						break;
					}
				}
			}
			else
			{
				isSameType = false;
			}

			if ( isSameType )
				return obArray;
			else if ( obj instanceof Boolean )
				populateObArray( obArray[1], obArray );
			else
				populateObArray( obj, obArray );
			return obArray;
		}

		private static Object[] populateObArray( Object obj, Object[] obArray )
		{
			try
			{
				if ( obj instanceof Number && !( obj instanceof BigDecimal ) )
				{
					for ( int i = 0; i < obArray.length; i++ )
					{
						obArray[i] = DataTypeUtil.toDouble( obArray[i] );
					}
				}
				else if ( obj instanceof java.sql.Date )
				{
					for ( int i = 0; i < obArray.length; i++ )
					{
						obArray[i] = DataTypeUtil.toSqlDate( obArray[i] );
					}
				}
				else if ( obj instanceof java.sql.Time )
				{
					for ( int i = 0; i < obArray.length; i++ )
					{
						obArray[i] = DataTypeUtil.toSqlTime( obArray[i] );
					}
				}
				else if ( obj instanceof Date )
				{
					for ( int i = 0; i < obArray.length; i++ )
					{
						obArray[i] = DataTypeUtil.toDate( obArray[i] );
					}
				}
			}
			catch ( BirtException e )
			{
				// If failed to convert to same date type for comparation,
				// simply convert them to String.
				try
				{
					makeObjectArrayStringArray( obArray );
				}
				catch ( BirtException e1 )
				{
					//should never reach here.
				}
			}
			// obArray will remain the same if obj is String rather than
			// Date,Number or Boolean
			return obArray;
		}

		/**
		 * 
		 * @param obArray
		 * @throws BirtException 
		 */
		private static void makeObjectArrayStringArray( Object[] obArray ) throws BirtException
		{
			for ( int i = 0; i < obArray.length; i++ )
			{
				if ( obArray[i] != null )
					obArray[i] = DataTypeUtil.toString( obArray[i] );
			}
		}
		
		/**
		 * @param ise
		 * @return
		 */
		private static IScriptExpression constructValidScriptExpression ( IScriptExpression ise)
		{
		    return ise!=null && ise.getText().trim().length() > 0 ? ise : new ScriptExpression("null");
		}
		
		/**
		 * 
		 * @return
		 */
		private static Object[] flatternMultipleValues( Object[] values )
		{
			if ( values == null || values.length == 0 )
				return new Object[0];
			List flattern = new ArrayList( );
			for ( int i = 0; i < values.length; i++ )
			{
				if ( values[i] instanceof Object[] )
				{
					Object[] flatternObj = (Object[]) values[i];
					flattern.addAll( Arrays.asList( flatternMultipleValues( flatternObj ) ) );
				}
				else
				{
					flattern.add( values[i] );
				}
			}
			return flattern.toArray( );
		}
	}
}

