/*

Derby - Class org.apache.derbyTesting.functionTests.util

Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements.  See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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.apache.derbyTesting.functionTests.util;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.math.BigDecimal;
import java.lang.reflect.*;

/**
 *  BigDecimalHandler provides wrappers for JDBC API methods which use BigDecimal.
 *  When writing tests which use BigDecimal, the methods in this class can be called
 *  instead of directly calling JDBC methods. This way the same test can be used in JVMs 
 *  like J2ME/CDC/Foundation Profile 1.0, which do not have BigDecimal class, or
 *  JSR169 Profile, which does not support method calls using BigDecimal (such 
 *  as ResultSet.getBigDecimal(..).
 *  
 * 
 *
 */
public class BigDecimalHandler {
	
	public static int representation;
	public static final int STRING_REPRESENTATION = 1;
	public static final int BIGDECIMAL_REPRESENTATION = 2;
	
	static{
		try{
			Class.forName("java.math.BigDecimal");
			representation = BIGDECIMAL_REPRESENTATION;
			// This class will attempt calls to ResultSet.getBigDecimal,
			// which may not be available with jvms that support JSR169,
			// even if BigDecimal itself has been made available (e.g. 
			// supporting J2ME/CDC/Foundation Profile 1.1).
			Method getbd = ResultSet.class.getMethod("getBigDecimal", new Class[] {int.class});
			representation = BIGDECIMAL_REPRESENTATION;
		}
		catch(ClassNotFoundException e){
			//Used for J2ME/Foundation
			representation = STRING_REPRESENTATION;
		}
		catch(NoSuchMethodException e){
			//Used for J2ME/Foundation
			representation = STRING_REPRESENTATION;
		}

	}
	
	//Type conversions supported by ResultSet getBigDecimal method - JDBC3.0 Table B-6 
	private static final int[] bdConvertibleTypes = 
	{	java.sql.Types.TINYINT,
		java.sql.Types.SMALLINT,
		java.sql.Types.INTEGER,
		java.sql.Types.BIGINT,
		java.sql.Types.REAL,
		java.sql.Types.FLOAT,
		java.sql.Types.DOUBLE,
		java.sql.Types.DECIMAL,
		java.sql.Types.NUMERIC,
		java.sql.Types.BIT,
		//java.sql.Types.BOOLEAN,	//Not supported in jdk13
		java.sql.Types.CHAR,
		java.sql.Types.VARCHAR,
		java.sql.Types.LONGVARCHAR
	};
	
	/** This method is a wrapper for the ResultSet method getBigDecimal(int columnIndex).
	 * 
	 * @param rs ResultSet 
	 * @param columnIndex Column Index 
	 * @return String value of getXXX(columnIndex)method on the ResultSet
	 * @throws SQLException
	 */
	public static String getBigDecimalString(ResultSet rs, int columnIndex) throws SQLException{
		String bigDecimalString=null;
		
		switch(representation){
			case BIGDECIMAL_REPRESENTATION:
				//Call toString() only for non-null values, else return null
				if(rs.getBigDecimal(columnIndex) != null)
					bigDecimalString = rs.getBigDecimal(columnIndex).toString();
				break;
			case STRING_REPRESENTATION:
				bigDecimalString = rs.getString(columnIndex);
				int columnType= rs.getMetaData().getColumnType(columnIndex);
				if((bigDecimalString != null) && !canConvertToDecimal(columnType))
					throw new SQLException("Invalid data conversion. Method not called.");
				break;
			default:	
				new Exception("Failed: Invalid Big Decimal representation").printStackTrace();
		}
		return bigDecimalString;
	}
	
	/** This method is a wrapper for ResultSet method getBigDecimal(String columnName).
	 * 
	 * @param rs ResultSet
	 * @param columnName Column Name
	 * @param columnIndex Coulumn Index
	 * @return String value of getXXX(columnName)method on the ResultSet
	 * @throws SQLException
	 */
	public static String getBigDecimalString(ResultSet rs, String columnName, int columnIndex) throws SQLException{
		String bigDecimalString = null;
				
		switch(representation){
			case BIGDECIMAL_REPRESENTATION:
				//Call toString() only for non-null values, else return null
				if(rs.getBigDecimal(columnName) != null){
					bigDecimalString = rs.getBigDecimal(columnName).toString();
				}
				break;
			case STRING_REPRESENTATION:
				bigDecimalString = rs.getString(columnName);
				int columnType= rs.getMetaData().getColumnType(columnIndex);
				if((bigDecimalString != null) && !canConvertToDecimal(columnType))
					throw new SQLException("Invalid data conversion. Method not called.");
				break;
			default:	
				new Exception("Failed: Invalid Big Decimal representation").printStackTrace();
		}
		return bigDecimalString;
	}
	
	/** This method is a wrapper for ResultSet method getObject(int columnIndex) 
	 * 
	 * @param rs ResultSet
	 * @param columnIndex ColumnIndex
	 * @return String value of getXXX(columnIndex) method on the ResultSet
	 * @throws SQLException
	 */
	public static String getObjectString(ResultSet rs, int columnIndex) throws SQLException{
		String objectString = null;
		
		switch(representation){
			case BIGDECIMAL_REPRESENTATION:
				//Call toString() only for non-null values, else return null
				if(rs.getObject(columnIndex) != null)
					objectString = rs.getObject(columnIndex).toString();
				break;
			case STRING_REPRESENTATION:
				int columnType= rs.getMetaData().getColumnType(columnIndex);
				if(columnType == java.sql.Types.DECIMAL){
					objectString = rs.getString(columnIndex);
				}	
				else
					//Call toString() only for non-null values, else return null
					if(rs.getObject(columnIndex) != null)
						objectString = rs.getObject(columnIndex).toString();
					break;
			default:	
				new Exception("Failed: Invalid Big Decimal representation").printStackTrace();
		}
		return objectString;
	}	
	
	/** This method is a wrapper for ResultSet method getObject(String columnName)
	 * @param rs ResultSet
	 * @param columnName Column Name
	 * @param columnIndex Column Index
	 * @return String value of getXXX(columnName) method on the ResultSet
	 * @throws SQLException
	 */
	public static String getObjectString(ResultSet rs, String columnName, int columnIndex) throws SQLException{
		String objectString = null;
				
		switch(representation){
			case BIGDECIMAL_REPRESENTATION:
				//Call toString() only for non-null values, else return null
				if(rs.getObject(columnName) != null)
					objectString = rs.getObject(columnName).toString();
				break;
			case STRING_REPRESENTATION:
				int columnType= rs.getMetaData().getColumnType(columnIndex);
				if(columnType == java.sql.Types.DECIMAL){
					objectString = rs.getString(columnName);
				}	
				else
					//Call toString() only for non-null values, else return null					
					if(rs.getObject(columnName) != null)
						objectString = rs.getObject(columnName).toString();
					break;
			default:	
				new Exception("Failed: Invalid Big Decimal representation").printStackTrace();
		}
		return objectString;
	}
	
	/** This method is a wrapper for ResultSet method 
	 * updateBigDecimal(int columnIndex, BigDecimal x)
	 * @param rs ResultSet
	 * @param columnIndex Column Index
	 * @param bdString String to be used in updateXXX method
	 * @throws SQLException
	 */
	public static void updateBigDecimalString(ResultSet rs, int columnIndex, String bdString) throws SQLException{
				
		switch(representation){
			case BIGDECIMAL_REPRESENTATION:
				BigDecimal bd = (bdString == null) ? null : new BigDecimal(bdString);
				rs.updateBigDecimal(columnIndex, bd);
				break;
			case STRING_REPRESENTATION:
				rs.updateString(columnIndex, bdString);
				break;
			default:	
				new Exception("Failed: Invalid Big Decimal representation").printStackTrace();
		}
	}
	
	/** This method is a wrapper for ResultSet method 
	 * updateBigDecimal(String columnName, BigDecimal x)
	 * @param rs ResultSet
	 * @param columnName Column Name
	 * @param bdString String to be used in updateXXX method
	 * @throws SQLException
	 */
	public static void updateBigDecimalString(ResultSet rs, String columnName,String bdString) throws SQLException{
				
		switch(representation){
			case BIGDECIMAL_REPRESENTATION:
				BigDecimal bd = (bdString == null) ? null : new BigDecimal(bdString);
				rs.updateBigDecimal(columnName, bd);
				break;
			case STRING_REPRESENTATION:
				rs.updateString(columnName, bdString);
				break;
			default:	
				new Exception("Failed: Invalid Big Decimal representation").printStackTrace();
		}
	}

	/** This method is a wrapper for the CallableStatement method getBigDecimal(int parameterIndex).
	 * The wrapper method needs the parameterType as an input since ParameterMetaData is not available in JSR169.
	 * 
	 * @param cs CallableStatement 
	 * @param parameterIndex Parameter Index
	 * @param parameterType Parameter Type
	 * @return String value of getXXX(parameterIndex)method on the CallableStatement
	 * @throws SQLException
	 */
	public static String getBigDecimalString(CallableStatement cs, int parameterIndex, int parameterType) throws SQLException{
		String bigDecimalString = null;
		
		switch(representation){
			case BIGDECIMAL_REPRESENTATION:
				//Call toString() only for non-null values, else return null
				if(cs.getBigDecimal(parameterIndex) != null)
					bigDecimalString = cs.getBigDecimal(parameterIndex).toString();
				break;
			case STRING_REPRESENTATION:
				bigDecimalString = cs.getString(parameterIndex);
				if((bigDecimalString != null) && !canConvertToDecimal(parameterType))
					throw new SQLException("Invalid data conversion. Method not called.");
				break;
			default:	
				new Exception("Failed: Invalid Big Decimal representation").printStackTrace();
		}
		return bigDecimalString;
	}	

	/** This method is a wrapper for the PreparedStatement method setBigDecimal(int parameterIndex,BigDecimal x)
	 * 
	 * @param ps PreparedStatement 
	 * @param parameterIndex Parameter Index
	 * @param bdString String to be used in setXXX method
	 * @throws SQLException
	 */
	public static void setBigDecimalString(PreparedStatement ps, int parameterIndex, String bdString) throws SQLException{
		
		switch(representation){
			case BIGDECIMAL_REPRESENTATION:
				BigDecimal bd = (bdString == null) ? null : new BigDecimal(bdString);
				ps.setBigDecimal(parameterIndex, bd);
				break;
			case STRING_REPRESENTATION:
				//setString is used since setBigDecimal is not available in JSR169
				//If bdString cannot be converted to short,int or long, this will throw
				//"Invalid character string format exception" 
				ps.setString(parameterIndex,bdString);
				break;
			default:	
				new Exception("Failed: Invalid Big Decimal representation").printStackTrace();
		}
	}
	
	/** This method is a wrapper for the PreparedStatement method setObject(int parameterIndex, Object x) 
	 * 
	 * @param ps PreparedStatement 
	 * @param parameterIndex Parameter Index
	 * @param objectString String to be used in setObject method
	 * @throws SQLException
	 */
	public static void setObjectString(PreparedStatement ps, int parameterIndex, String objectString) throws SQLException{
		
		switch(representation){
			case BIGDECIMAL_REPRESENTATION:
				BigDecimal bd = (objectString == null) ? null : new BigDecimal(objectString);
				ps.setObject(parameterIndex,bd);
				break;
			case STRING_REPRESENTATION:
				ps.setObject(parameterIndex,objectString);
				break;
			default:	
				new Exception("Failed: Invalid Big Decimal representation").printStackTrace();
		}
	}	
	
	/** This method checks that the SQL type can be converted to Decimal
	 * 
	 * @param type the type to check
	 * @return true if the SQL type is convertible to DECIMAL, false otherwise.
	 * @throws SQLException
	 */
	protected static boolean canConvertToDecimal(int type) throws SQLException{
		boolean  canConvert = false;
		
		for (int bdType = 0; bdType < bdConvertibleTypes.length; bdType++){
			if(type == bdConvertibleTypes[bdType]){
				canConvert = true;
				break;
			}
		}
		
		return canConvert;
	}
	
}
