/*

   Derby - Class org.apache.derby.impl.sql.compile.MethodCallNode

   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.derby.impl.sql.compile;

import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
import java.sql.ParameterMetaData;
import java.sql.ResultSet;
import java.util.List;
import java.util.StringTokenizer;
import org.apache.derby.catalog.TypeDescriptor;
import org.apache.derby.catalog.types.RoutineAliasInfo;
import org.apache.derby.catalog.types.TypeDescriptorImpl;
import org.apache.derby.catalog.types.UserDefinedTypeIdImpl;
import org.apache.derby.shared.common.error.StandardException;
import org.apache.derby.shared.common.reference.SQLState;
import org.apache.derby.iapi.services.classfile.VMOpcode;
import org.apache.derby.iapi.services.compiler.LocalField;
import org.apache.derby.iapi.services.compiler.MethodBuilder;
import org.apache.derby.iapi.services.context.ContextManager;
import org.apache.derby.iapi.services.loader.ClassInspector;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.sql.compile.CompilerContext;
import org.apache.derby.iapi.sql.compile.TypeCompiler;
import org.apache.derby.iapi.sql.compile.TypeCompilerFactory;
import org.apache.derby.iapi.sql.compile.Visitor;
import org.apache.derby.iapi.store.access.Qualifier;
import org.apache.derby.iapi.types.DataTypeDescriptor;
import org.apache.derby.iapi.types.JSQLType;
import org.apache.derby.iapi.types.TypeId;
import org.apache.derby.iapi.util.JBitSet;

/**
 * A MethodCallNode represents a Java method call.  Method calls can be done
 * through DML (as expressions) or through the CALL statement.
 *
 */

abstract class MethodCallNode extends JavaValueNode
{
	/*
	** Name of the method.
	*/
	 String	methodName;

    /** The name of the class containing the method. May not be known until bindExpression() has been called.
     * @see #bindExpression
     * @see #getJavaClassName()
     */
    String javaClassName;
	
	/**
		For a procedure or function call
	*/
	RoutineAliasInfo routineInfo;


	/**
		True if this is an internal call, just used to set up a generated method call.
	*/
	boolean internalCall;

	/**
		For resolution of procedure INOUT/OUT parameters to the primitive
		form, such as int[]. May be null.
	*/
	private String[] procedurePrimitiveArrayType;

	// bound signature of arguments, stated in universal types (JSQLType)
	protected JSQLType[]				signature;

	/*
	** Parameters to the method, if any.  No elements if no parameters.
	*/
	protected JavaValueNode[]	methodParms;

	/* The method call */
	protected Member method;

	protected String actualMethodReturnType;

	/**
		The parameter types for the resolved method.
	*/
	String[] methodParameterTypes;

    MethodCallNode(String methodName, ContextManager cm) {
        super(cm);
        this.methodName = methodName;
    }

    String getMethodName()
	{
		return  methodName;
	}

    /**
     * <p>
     * Get the schema-qualified name of the the routine. Is non-null only for
     * StaticMethodCallNodes.
     * </p>
     */
    TableName getFullName()
	{
		return  null;
	}

    /**
     * @return the name of the class that contains the method, null if not known. It may not be known
     *         until this node has been bound.
     */
    public String getJavaClassName()
    {
        return javaClassName;
    }

    /**
     * @return get the Java method or constructor determined during the bind() phase.
     */
    public Member getResolvedMethod()
    {
        return method;
    }

    /**
     * Get the details on the invoked routines.
     */
    public RoutineAliasInfo getRoutineInfo()
    {
        return routineInfo;
    }

	/**
	 * Add the parameter list
	 *
	 * @param parameterList		A list of the parameters
	 *
	 * @exception StandardException		Thrown on error
	 */
    void addParms(List<ValueNode> parameterList) throws StandardException
	{
		methodParms = new JavaValueNode[parameterList.size()];

		int	plSize = parameterList.size();
		for (int index = 0; index < plSize; index++)
		{
            ValueNode qt = parameterList.get(index);

			/*
			** Since we need the parameter to be in Java domain format, put a
            ** SQLToJavaValueNode on top of the ValueNode parameter node.
			*/
            JavaValueNode jqt =
                new SQLToJavaValueNode(qt, getContextManager());

            methodParms[index] = jqt;
		}
	}

	/**
	  *	Get the resolved Classes of our parameters
	  *
	  *	@return	the Classes of our parameters
	  */
    Class<?>[]  getMethodParameterClasses()
	{ 
		ClassInspector ci = getClassFactory().getClassInspector();

        Class<?>[]  parmTypeClasses = new Class<?>[methodParms.length];

		for (int i = 0; i < methodParms.length; i++)
		{
			String className = methodParameterTypes[i];
			try
			{
				parmTypeClasses[i] = ci.getClass(className);
			}
			catch (ClassNotFoundException cnfe)
			{
				/* We should never get this exception since we verified 
				 * that the classes existed at bind time.  Just return null.
				 */
				if (SanityManager.DEBUG)
				{
					SanityManager.THROWASSERT("Unexpected exception", cnfe);
				}
				return null;
			}
		}

		return parmTypeClasses;
	}

	/**
	 * Build a JBitSet of all of the tables that we are
	 * correlated with.
	 *
	 * @param correlationMap	The JBitSet of the tables that we are correlated with.
	 */
	void getCorrelationTables(JBitSet correlationMap)
		throws StandardException
	{
        CollectNodesVisitor<ColumnReference> getCRs =
            new CollectNodesVisitor<ColumnReference>(ColumnReference.class);
		accept(getCRs);
        for (ColumnReference ref : getCRs.getList())
		{
			if (ref.getCorrelated())
			{
				correlationMap.set(ref.getTableNumber());
			}
		}
	}

	/**
	 * Prints the sub-nodes of this object.  See QueryTreeNode.java for
	 * how tree printing is supposed to work.
	 *
	 * @param depth		The depth of this node in the tree
	 */
    @Override
    void printSubNodes(int depth)
	{
		if (SanityManager.DEBUG)
		{
			int	parm;

			super.printSubNodes(depth);
			if (methodParms != null)
			{
				for (parm = 0; parm < methodParms.length; parm++)
				{
					if (methodParms[parm] != null)
					{
						printLabel(depth, "methodParms[" + parm + "] :");
						methodParms[parm].treePrint(depth + 1);
					}
				}
			}
		}
	}

	/**
	 * Convert this object to a String.  See comments in QueryTreeNode.java
	 * for how this should be done for tree printing.
	 *
	 * @return	This object as a String
	 */
    @Override
	public String toString()
	{
		if (SanityManager.DEBUG)
		{
			return "methodName: " +
					(methodName != null ? methodName : "null") + "\n" +
					super.toString();
		}
		else
		{
			return "";
		}
	}

	/**
	 * Bind this expression.  This means binding the sub-expressions,
	 * as well as figuring out what the return type is for this expression.
	 *
	 * @param fromList		The FROM list for the query this
	 *				expression is in, for binding columns.
	 * @param subqueryList		The subquery list being built as we find SubqueryNodes
     * @param aggregates        The aggregate list being built as we find AggregateNodes
	 *
	 * @exception StandardException		Thrown on error
	 */
	final void bindParameters(
        FromList fromList, SubqueryList subqueryList, List<AggregateNode> aggregates)
			throws StandardException
	{
		/* Bind the parameters */
		if (methodParms != null)
		{
			int		count = methodParms.length;

			// with a procedure call the signature
			// is preformed in StaticMethodCall from
			// the procedures signature.
			if (signature == null) 
				signature = new JSQLType[ count ];

			for (int parm = 0; parm < count; parm++)
			{
				if (methodParms[parm] != null)
				{
					methodParms[parm] =
						methodParms[parm].bindExpression(
                            fromList, subqueryList, aggregates);

					if (routineInfo == null)
						signature[ parm ] = methodParms[ parm ].getJSQLType();
                    
                    SelectNode.checkNoWindowFunctions(methodParms[parm], "method argument");
				}
			}
		}
	}

	/**
	 * Return whether or not all of the parameters to this node are
	 * QUERY_INVARIANT or CONSTANT.  This is useful for VTIs - a VTI is a candidate
	 * for materialization if all of its parameters are QUERY_INVARIANT or CONSTANT
	 *
	 * @return Whether or not all of the parameters to this node are QUERY_INVARIANT or CONSTANT
	 * @exception StandardException	thrown on error
	 */
	 protected boolean areParametersQueryInvariant() throws StandardException
	 {
		return (getVariantTypeOfParams() == Qualifier.QUERY_INVARIANT);
	 }

	/**
	 * Build parameters for error message and throw the exception when there
	 * is no matching signature found.
	 *
	 * @param receiverTypeName	Type name for receiver
	 * @param parmTypeNames		Type names for parameters as object types
	 * @param primParmTypeNames	Type names for parameters as primitive types
	 *
	 * @exception StandardException		Thrown on error
	 */
	void throwNoMethodFound(String receiverTypeName,
									  String[] parmTypeNames,
									  String[] primParmTypeNames)
		throws StandardException
	{
		/* Put the parameter type names into a single string */
        StringBuilder   parmTypes = new StringBuilder();
        int     paramCount = signature.length;
		for (int i = 0; i < paramCount; i++)
		{
			if (i != 0) { parmTypes.append(", "); }
            boolean isVararg = isVararg( i );

			/* RESOLVE - shouldn't be using hard coded strings for output */
            String  parmType = parmTypeNames[ i ];
            if ( parmTypeNames [i ].length() == 0 ) { parmType = "UNTYPED"; }
            else if ( isVararg ) { parmType = getVarargTypeName( parmType ); }

            parmTypes.append( parmType );

			if ((primParmTypeNames != null) &&
				! primParmTypeNames[i].equals(parmTypeNames[i]))  // has primitive
            {
                String  primTypeName = primParmTypeNames[ i ];
                if ( isVararg ) { primTypeName = getVarargTypeName( primTypeName ); }
				parmTypes.append("(" + primTypeName + ")");
            }
		}

		throw StandardException.newException(SQLState.LANG_NO_METHOD_FOUND, 
												receiverTypeName,
												methodName,
											 	parmTypes);
	}

    /** Turn an array type name into the corresponding vararg type name */
    private String  getVarargTypeName( String arrayTypeName )
    {
        return stripOneArrayLevel( arrayTypeName ) + "...";
    }

	/**
	 * Preprocess an expression tree.  We do a number of transformations
	 * here (including subqueries, IN lists, LIKE and BETWEEN) plus
	 * subquery flattening.
	 * NOTE: This is done before the outer ResultSetNode is preprocessed.
	 *
	 * @param	numTables			Number of tables in the DML Statement
	 * @param	outerFromList		FromList from outer query block
	 * @param	outerSubqueryList	SubqueryList from outer query block
	 * @param	outerPredicateList	PredicateList from outer query block
	 *
	 * @exception StandardException		Thrown on error
	 */
    void preprocess(int numTables,
							FromList outerFromList,
							SubqueryList outerSubqueryList,
							PredicateList outerPredicateList) 
					throws StandardException
	{
		int	parm;

		/* Preprocess the parameters */
		if (methodParms != null)
		{
			for (parm = 0; parm < methodParms.length; parm++)
			{
				if (methodParms[parm] != null)
				{
					methodParms[parm].preprocess(numTables,
												 outerFromList,
												 outerSubqueryList,
												 outerPredicateList);
				}
			}
		}
	}

	/**
	 * Categorize this predicate.  Initially, this means
	 * building a bit map of the referenced tables for each predicate.
	 * If the source of this ColumnReference (at the next underlying level) 
	 * is not a ColumnReference or a VirtualColumnNode then this predicate
	 * will not be pushed down.
	 *
	 * For example, in:
	 *		select * from (select 1 from s) a (x) where x = 1
	 * we will not push down x = 1.
	 * NOTE: It would be easy to handle the case of a constant, but if the
	 * inner SELECT returns an arbitrary expression, then we would have to copy
	 * that tree into the pushed predicate, and that tree could contain
	 * subqueries and method calls.
	 * RESOLVE - revisit this issue once we have views.
	 *
	 * @param referencedTabs	JBitSet with bit map of referenced FromTables
	 * @param simplePredsOnly	Whether or not to consider method
	 *							calls, field references and conditional nodes
	 *							when building bit map
	 *
	 * @return boolean		Whether or not source.expression is a ColumnReference
	 *						or a VirtualColumnNode.
	 * @exception StandardException			Thrown on error
	 */
    boolean categorize(JBitSet referencedTabs, boolean simplePredsOnly)
		throws StandardException
	{
		/* We stop here when only considering simple predicates
		 *  as we don't consider method calls when looking
		 * for null invariant predicates.
		 */
		if (simplePredsOnly)
		{
			return false;
		}

		boolean pushable = true;
		int		param;

		if (methodParms != null)
		{
			for (param = 0; param < methodParms.length; param++)
			{
				if (methodParms[param] != null)
				{
					pushable = methodParms[param].categorize(referencedTabs, simplePredsOnly) &&
							   pushable;
				}
			}
		}

		/* We need to push down method call.  Then the predicate can be used for start/stop
		 * key for index scan.  The fact that method call's cost is not predictable and can
		 * be expensive doesn't mean we shouldn't push it down. Beetle 4826.
		 */
		return pushable;
	}

	/**
	 * Remap all ColumnReferences in this tree to be clones of the
	 * underlying expression.
	 *
	 * @return JavaValueNode			The remapped expression tree.
	 *
	 * @exception StandardException			Thrown on error
	 */
    JavaValueNode remapColumnReferencesToExpressions()
		throws StandardException
	{
		int	param;

		if (methodParms != null)
		{
			for (param = 0; param < methodParms.length; param++)
			{
				if (methodParms[param] != null)
				{
					methodParms[param] =
						methodParms[param].remapColumnReferencesToExpressions();
				}
			}
		}
		return this;
	}

    /** Return true if the routine has varargs */
    public  boolean hasVarargs()
    {
        return (routineInfo == null ) ? false : routineInfo.hasVarargs();
    }

    /** Get the index of the first vararg if this is a varargs method */
    public  int getFirstVarargIdx() { return signature.length - 1; }

    /** Return true if the parameter is a vararg */
    public  boolean isVararg( int parameterNumber )
    {
        if ( !hasVarargs() ) { return false; }
        else
        {
            return ( parameterNumber >= getFirstVarargIdx() );
        }
    }

	/**
	 * Generate the parameters to the given method call
	 *
	 * @param acb	The ExpressionClassBuilder for the class we're generating
	 * @param mb the method  the expression will go into
	 *
	 * @return	Count of arguments to the method.
	 *
	 * @exception StandardException		Thrown on error
	 */

	public	int generateParameters(ExpressionClassBuilder acb,
											MethodBuilder mb)
			throws StandardException
	{
		int				param;

        int                 nonVarargCount = hasVarargs() ? routineInfo.getParameterCount() - 1 : methodParms.length;
        int                 totalArgCount = hasVarargs() ? nonVarargCount + 1 : nonVarargCount;

		/* Generate the code for each user parameter, generating the appropriate
		 * cast when the passed type needs to get widened to the expected type.
		 */
		for ( param = 0; param < nonVarargCount; param++ )
		{
            generateAndCastOneParameter( acb, mb, param, methodParameterTypes[ param ] );
		}

        if ( hasVarargs() ) { generateVarargs( acb, mb ); }

		return totalArgCount;
	}

    /**
     * <p>
     * Generate and cast one parameter, pushing the result onto the stack.
     * </p>
     */
    private void    generateAndCastOneParameter
        ( ExpressionClassBuilder acb, MethodBuilder mb, int param, String parameterType )
        throws StandardException
    {
		ClassInspector classInspector = getClassFactory().getClassInspector();

        generateOneParameter( acb, mb, param );

        // type from the SQL-J expression
        String argumentType = getParameterTypeName( methodParms[param] );

        if (!parameterType.equals(argumentType))
        {
            //
            // This handles the conversion from primitive to wrapper type. See DERBY-6511.
            // If the parameter type is the wrapper form of the primitive argument type,
            // then call the "valueOf" static method of the wrapper type in order to convert
            // the argument into a wrapper object. So, for instance, this converts a primitive "int"
            // into a "java.lang.Integer".
            //
            if (
                ClassInspector.primitiveType( argumentType ) &&
                parameterType.equals( JSQLType.getWrapperClassName( JSQLType.getPrimitiveID( argumentType ) ) )
                )
            {
                // short must be converted to int
                if ( "short".equals( argumentType ) )
                {
                    mb.cast( "int" );
                }
                
                mb.callMethod
                    (
                     VMOpcode.INVOKESTATIC,
                     parameterType,
                     "valueOf",
                     parameterType,
                     1
                     );
            }
            // since we reached here through method resolution
            // casts are only required for primitive types.
            // In any other case the expression type must be assignable
            // to the parameter type.
            else if (ClassInspector.primitiveType(parameterType))
            {
                mb.cast(parameterType);
            } else
            {
                // for a procedure
                if (routineInfo != null)
                {
                    return; // probably should be only for INOUT/OUT parameters.
                }

                if (SanityManager.DEBUG)
                {
                    SanityManager.ASSERT(classInspector.assignableTo(argumentType, parameterType),
                                         "Argument type " + argumentType + " is not assignable to parameter " + parameterType);
                }

                /*
                ** Set the parameter type in case the argument type is narrower
                ** than the parameter type.
                */
                mb.upCast(parameterType);
            }
        }
    }

    /**
     * <p>
     * Generate the trailing routine arguments into a varargs array and
     * push that array onto the stack.
     * </p>
     */
    private void    generateVarargs
        ( ExpressionClassBuilder acb, MethodBuilder mb )
        throws StandardException
    {
        // the vararg is the last declared arg of the Java method. it is always
        // an array type. right now we only support vararg static methods.
        // if we have to support vararg constructors in the future, then this code
        // will need adjustment.
        int         firstVarargIdx = getFirstVarargIdx();
        String      arrayType = methodParameterTypes[ firstVarargIdx ];
        String      cellType = stripOneArrayLevel( arrayType );
        String      varargType = cellType;

        // must strip another array level off of out and in/out parameters
        if ( routineInfo != null )
        {
            if ( routineInfo.getParameterModes()[ firstVarargIdx ] != (ParameterMetaData.parameterModeIn) )
            {
                varargType = stripOneArrayLevel( varargType );
            }
        }

        int         varargCount = methodParms.length - firstVarargIdx;
        if ( varargCount < 0 ) { varargCount = 0; }

        // allocate an array to hold the varargs
		LocalField arrayField = acb.newFieldDeclaration( Modifier.PRIVATE, arrayType );
		MethodBuilder cb = acb.getConstructor();
		cb.pushNewArray( cellType, varargCount );
		cb.setField( arrayField );

        // now put the arguments into the array
        for ( int i = 0; i < varargCount; i++ )
        {
			mb.getField( arrayField ); // push the array onto the stack
            // evaluate the parameter and push it onto the stack
            generateAndCastOneParameter( acb, mb, i + firstVarargIdx, cellType );
            mb.setArrayElement( i ); // move the parameter into the array, pop the stack
        }
        
        // push the array onto the stack. it is the last parameter to the varargs routine.
        mb.getField( arrayField );
    }

    /**
     * <p>
     * Get the offset into the routine arguments corresponding to the index
     * of the invocation parameter. The two indexes may be different in the case of
     * varargs methods. There may be more invocation args than declared routine args.
     * For a varargs routine, all of the trailing invocation parameters correspond to the
     * last argument declared by the CREATE FUNCTION/PROCEDURE statement.
     * </p>
     */
    protected   int getRoutineArgIdx( int invocationArgIdx )
    {
        if ( routineInfo == null ) { return invocationArgIdx; }
        else { return getRoutineArgIdx( routineInfo, invocationArgIdx ); }
    }
    protected   int getRoutineArgIdx( RoutineAliasInfo rai, int invocationArgIdx )
    {
        if ( !rai.hasVarargs() ) { return invocationArgIdx; }

        // ok, this is a varargs routine
        int         firstVarargIdx = rai.getParameterCount() - 1;

        return (firstVarargIdx < invocationArgIdx) ? firstVarargIdx : invocationArgIdx;
    }
    

	static	public	String	getParameterTypeName( JavaValueNode param )
		throws StandardException
	{
		String	argumentType;

		// RESOLVE - shouldn't this logic be inside JavaValueNode ??
		// I.e. once the value is primitive then its java type name is its
		// primitive type name.
		if (param.isPrimitiveType()) { argumentType = param.getPrimitiveTypeName(); }
		else { argumentType = param.getJavaTypeName(); }

		return	argumentType;
	}

	/**
	 * Generate one parameter to the given method call. This method is overriden by
	 * RepStaticMethodCallNode.
	 *
	 * @param acb				The ExpressionClassBuilder for the class we're generating
	 * @param mb the method the expression will go into
	 * @param parameterNumber	Identifies which parameter to generate. 0 based.
	 *
	 * @exception StandardException		Thrown on error
	 */

    void generateOneParameter(ExpressionClassBuilder acb,
											MethodBuilder mb,
											int parameterNumber )
			throws StandardException
	{
		methodParms[parameterNumber].generateExpression(acb, mb);
	}

	/**
	 * Set the appropriate type information for a null passed as a parameter.
	 * This method is called after method resolution, when a signature was
	 * successfully matched.
	 *
	 * @param parmTypeNames	String[] with the java type names for the parameters
	 *        as declared by the method
	 *
	 * @exception StandardException		Thrown on error
	 */
    void    setNullParameterInfo(String[] parmTypeNames)
			throws StandardException
	{
		for (int i = 0; i < methodParms.length; i++)
		{
			/* null parameters are represented by a java type name of "" */
			if (methodParms[i].getJavaTypeName().equals(""))
			{		
				/* Set the type information in the null constant node */
				DataTypeDescriptor dts = DataTypeDescriptor.getSQLDataTypeDescriptor(parmTypeNames[i]);
				((SQLToJavaValueNode)methodParms[i]).value.setType(dts);

				/* Set the correct java type name */
				methodParms[i].setJavaTypeName(parmTypeNames[i]);
				signature[i] = methodParms[i].getJSQLType();
			}
		}
	}

	protected void resolveMethodCall
        (
         String javaClassName,
         boolean staticMethod
         ) 
        throws StandardException
	{
		// only allow direct method calls through routines and internal SQL.
		if (routineInfo == null && !internalCall)
		{
			// See if we are being executed in an internal context
			if ((getCompilerContext().getReliability() & CompilerContext.INTERNAL_SQL_ILLEGAL) != 0) {
				throw StandardException.newException(SQLState.LANG_SYNTAX_ERROR,  javaClassName + (staticMethod ? "::" : ".") + methodName);
			}
		}

		int			count = signature.length;

		ClassInspector classInspector = getClassFactory().getClassInspector();

		
		String[]		parmTypeNames;
		String[]		primParmTypeNames = null;
		boolean[]		isParam = getIsParam();

		boolean hasDynamicResultSets = hasVarargs() ?
            false :
            (routineInfo != null) && (count != 0) && (count != methodParms.length);

        /*
        ** Find the matching method that is public.
        */
        int signatureOffset = methodName.indexOf('(');
        	
        // support Java signatures by checking if the method name contains a '('
        if (signatureOffset != -1) {
            parmTypeNames = parseValidateSignature(methodName, signatureOffset, hasDynamicResultSets);
            methodName = methodName.substring(0, signatureOffset);
            
            // If the signature is specified then Derby resolves to exactly
            // that method. Setting this flag to false disables the method
            // resolution from automatically optionally repeating the last
            // parameter as needed.
            hasDynamicResultSets = false;
        }
        else
        {
            parmTypeNames = getObjectSignature();
        }

        // the actual type of the trailing Java varargs arg is an array
        if ( hasVarargs() )
        {
            parmTypeNames[ count - 1 ] = parmTypeNames[ count - 1 ] + "[]";
        }

        try
        {
            method = classInspector.findPublicMethod
                (
                 javaClassName,
                 methodName,
                 parmTypeNames,
                 null,
                 isParam,
                 staticMethod,
                 hasDynamicResultSets,
                 hasVarargs()
                 );

            // DB2 LUW does not support Java object types for SMALLINT, INTEGER, BIGINT, REAL, DOUBLE
            // and these are the only types that can map to a primitive or an object type according
            // to SQL part 13. So we never have a second chance match.
            // Also if the DDL specified a signature, then no alternate resolution
            if (signatureOffset == -1 && routineInfo == null) {

                /* If no match, then retry with combinations of object and
                 * primitive types.
                 */
                if (method == null)
                {
                    primParmTypeNames = getPrimitiveSignature(false);

                    method = classInspector.findPublicMethod
                        (
                         javaClassName,
                         methodName,
                         parmTypeNames,
                         primParmTypeNames,
                         isParam,
                         staticMethod,
                         hasDynamicResultSets,
                         hasVarargs()
                         );
                }
            }
        }
        catch (ClassNotFoundException e)
        {
            /*
            ** If one of the classes couldn't be found, just act like the
            ** method couldn't be found.  The error lists all the class names,
            ** which should give the user enough info to diagnose the problem.
            */
            method = null;
        }
		/* Throw exception if no matching signature found */
		if (method == null)
		{
			throwNoMethodFound(javaClassName, parmTypeNames, primParmTypeNames);
		}

		String	typeName = classInspector.getType(method);
		actualMethodReturnType = typeName;

		if (routineInfo == null) {

			/* void methods are only okay for CALL Statements */
			if (typeName.equals("void"))
			{
				if (!forCallStatement)
					throw StandardException.newException(SQLState.LANG_VOID_METHOD_CALL);
			}
		}
		else
		{
			String promoteName = null;
			TypeDescriptorImpl returnType = (TypeDescriptorImpl) routineInfo.getReturnType();
			String requiredType;
			if (returnType == null)
			{
				// must have a void method for a procedure call.
				requiredType = "void";
			}
			else
			{
				TypeId returnTypeId = TypeId.getBuiltInTypeId(returnType.getJDBCTypeId());

				if (
				    returnType.isRowMultiSet() &&
				    ( routineInfo.getParameterStyle() == RoutineAliasInfo.PS_DERBY_JDBC_RESULT_SET )
				)
				{
				    requiredType = ResultSet.class.getName();
				}
                else if ( returnType.getTypeId().userType() )
                {
                    requiredType = ((UserDefinedTypeIdImpl) returnType.getTypeId()).getClassName();
                }
				else
				{
			 		requiredType = returnTypeId.getCorrespondingJavaTypeName();

					if (!requiredType.equals(typeName)) {
						switch (returnType.getJDBCTypeId()) {
						case java.sql.Types.BOOLEAN:
						case java.sql.Types.SMALLINT:
						case java.sql.Types.INTEGER:
						case java.sql.Types.BIGINT:
						case java.sql.Types.REAL:
						case java.sql.Types.DOUBLE:
							TypeCompiler tc = getTypeCompiler(returnTypeId);
							requiredType = tc.getCorrespondingPrimitiveTypeName();
							if (!routineInfo.calledOnNullInput() && routineInfo.getParameterCount() != 0)
							{
								promoteName = returnTypeId.getCorrespondingJavaTypeName();
							}
							break;
						}
					}
				}
			}

            boolean foundCorrectType;
            if ( ResultSet.class.getName().equals( requiredType )  )
            {
                // allow subtypes of ResultSet too
                try {
                    Class<?> actualType = classInspector.getClass( typeName );

                    foundCorrectType = ResultSet.class.isAssignableFrom( actualType );
                }
                catch (ClassNotFoundException cnfe) { foundCorrectType = false; }
            }
            else{ foundCorrectType = requiredType.equals(typeName); }

			if (!foundCorrectType)
			{
				throwNoMethodFound(requiredType + " " + javaClassName, parmTypeNames, primParmTypeNames);
			}

			// for a returns null on null input with a primitive
			// type we need to promote to an object so we can return null.
			if (promoteName != null)
				typeName = promoteName;
			//propogate collation type from RoutineAliasInfo to
			// MethodCallNode DERBY-2972
                        if (routineInfo.getReturnType() != null)
                            setCollationType(routineInfo.getReturnType().getCollationType());     
                }
	 	setJavaTypeName( typeName );
                
		methodParameterTypes = classInspector.getParameterTypes(method);

        String methodParameter = null;
        
		for (int i = 0; i < methodParameterTypes.length; i++)
		{
			methodParameter = methodParameterTypes[i];

			if (routineInfo != null) {
				if (i < routineInfo.getParameterCount()) {
					int parameterMode = routineInfo.getParameterModes()[ getRoutineArgIdx( i ) ];

					switch (parameterMode) {
                    case (ParameterMetaData.parameterModeIn):
						break;
                    case (ParameterMetaData.parameterModeInOut):
						// we need to see if the type of the array is
						// primitive, not the array itself.
						methodParameter = stripOneArrayLevel( methodParameter );
						break;

                    case (ParameterMetaData.parameterModeOut):
						// value is not obtained *from* parameter.
						continue;
					}
				}
			}

            //
            // Strip off the array type if this is a varargs arg. We are only interested in
            // whether we need to cast to the cell type.
            //
            if ( hasVarargs() && (i >= getFirstVarargIdx()) )
            {
                methodParameter = stripOneArrayLevel( methodParameter );
            }

			if (ClassInspector.primitiveType(methodParameter))
            {
                // varargs may be omitted, so there may not be an invocation argument
                // corresponding to the vararg
                if ( i < methodParms.length )
                {
                    methodParms[i].castToPrimitive(true);
                }
            }
		}

        // the last routine parameter may have been a varargs. if so,
        // casting may be needed on the trailing varargs
        if ( hasVarargs() )
        {
            int     firstVarargIdx = getFirstVarargIdx();
            int     trailingVarargCount = methodParms.length - firstVarargIdx;

            // the first vararg was handled in the preceding loop
            for ( int i = 1; i < trailingVarargCount; i++ )
            {
                if (ClassInspector.primitiveType(methodParameter))
                {
                    methodParms[ i + firstVarargIdx ].castToPrimitive(true);
                }
            }
        }

		/* Set type info for any null parameters */
		if ( someParametersAreNull() )
		{
			setNullParameterInfo(methodParameterTypes);
		}


    
		/* bug 4450 - if the callable statement is ? = call form, generate the metadata
		infor for the return parameter. We don't really need that info in order to
		execute the callable statement. But with jdbc3.0, this information should be
		made available for return parameter through ParameterMetaData class.
		Parser sets a flag in compilercontext if ? = call. If the flag is set,
		we generate the metadata info for the return parameter and reset the flag
		in the compilercontext for future call statements*/
		DataTypeDescriptor dts = DataTypeDescriptor.getSQLDataTypeDescriptor(typeName);
		if (getCompilerContext().getReturnParameterFlag()) {
            getParameterTypes()[0] = dts;
		}
    }

    /** Strip the trailing [] from a type name */
    protected String  stripOneArrayLevel( String typeName )
    {
        return typeName.substring( 0, typeName.length() - 2 );
    }
	
	/**
	 * Parse the user supplied signature for a method and validate
	 * it, need to match the number of parameters passed in and match
	 * the valid types for the parameter.
	 * @param offset Character offset of first paren
	 * @param hasDynamicResultSets Can ResultSet[] parameters be specified.
	 * @return The valid array of types for resolution.
	 * @throws StandardException
	 */
	private String[] parseValidateSignature(String externalName, int offset,
			boolean hasDynamicResultSets)
		throws StandardException
	{
		int siglen = externalName.length();

		// Ensure the opening paren is not the last
		// character and that the last character is a close paren
		if (((offset + 1) == siglen)
			|| (externalName.charAt(siglen - 1) != ')'))
			throw StandardException.newException(SQLState.SQLJ_SIGNATURE_INVALID); // invalid
		
        StringTokenizer st = new StringTokenizer(externalName.substring(offset + 1, siglen - 1), ",", true);
        
        String[] signatureTypes = new String[signature.length];
        int count;
        boolean seenClass = false;
        for (count = 0; st.hasMoreTokens();)
        {
           	String type = st.nextToken().trim();
 
           	// check sequence is <class><comma>class> etc.
           	if (",".equals(type))
           	{
           		if (!seenClass)
           			throw StandardException.newException(SQLState.SQLJ_SIGNATURE_INVALID); // invalid
           		seenClass = false;
           		continue;
           	}
           	else
           	{
           		if (type.length() == 0)
           			throw StandardException.newException(SQLState.SQLJ_SIGNATURE_INVALID); // invalid
           		seenClass = true;
           		count++;
           	}
           	           	           	           
           	if (count > signature.length)
        	{
        		if (hasDynamicResultSets)
        		{
        			// Allow any number of dynamic result set holders
        			// but they must match the exact type.
        			String rsType = signature[signature.length - 1].getSQLType().
						getTypeId().getCorrespondingJavaTypeName();
        			
        			if (!type.equals(rsType))
        				throw StandardException.newException(SQLState.LANG_DATA_TYPE_GET_MISMATCH, 
                				type, rsType);

        			if (signatureTypes.length == signature.length)
        			{
        				// expand once
        				String[] sigs = new String[st.countTokens()];
        				System.arraycopy(signatureTypes, 0, sigs, 0, signatureTypes.length);
        				signatureTypes = sigs;
        			}
        			
            		signatureTypes[count - 1] = type;
            		continue;
       			
        		}
    			throw StandardException.newException(SQLState.SQLJ_SIGNATURE_PARAMETER_COUNT, 
        				Integer.toString(count),
        				Integer.toString(signature.length)); // too many types
        	}

        	       	
        	TypeId	paramTypeId = signature[count - 1].getSQLType().getTypeId();
        	        	
        	// Does it match the object name
        	if (type.equals(paramTypeId.getCorrespondingJavaTypeName()))
        	{
        		signatureTypes[count - 1] = type;
        		continue;
        	}
      	
        	// how about the primitive name
			if ((paramTypeId.isNumericTypeId() && !paramTypeId.isDecimalTypeId())
					|| paramTypeId.isBooleanTypeId())
			{
				TypeCompiler tc = getTypeCompiler(paramTypeId);
				if (type.equals(tc.getCorrespondingPrimitiveTypeName()))
				{
		       		signatureTypes[count - 1] = type;
	        		continue;					
				}
			}
        	throw StandardException.newException(SQLState.LANG_DATA_TYPE_GET_MISMATCH, 
        				type, paramTypeId.getSQLTypeName()); // type conversion error
        }
        
        // Did signature end with trailing comma?
        if (count != 0 && !seenClass)
        	throw StandardException.newException(SQLState.SQLJ_SIGNATURE_INVALID); // invalid
        
        if (count < signatureTypes.length)
        {
        	if (hasDynamicResultSets)
        	{
        		// we can tolerate a count of one less than the
        		// expected count, which means the procedure is declared
        		// to have dynamic result sets, but the explict signature
        		// doesn't have any ResultSet[] types.
        		// So accept, and procedure will automatically have 0
        		// dynamic results at runtime
        		if (count == (signature.length - 1))
        		{
        			String[] sigs = new String[count];
        			System.arraycopy(signatureTypes, 0, sigs, 0, count);
        			return sigs;
        		}
        	}
			throw StandardException.newException(SQLState.SQLJ_SIGNATURE_PARAMETER_COUNT, 
    				Integer.toString(count),
    				Integer.toString(signature.length)); // too few types
        }

        return signatureTypes;
	}

	/**
	  *	Return true if some parameters are null, false otherwise.
	  */
	protected	boolean	someParametersAreNull()
	{
		int		count = signature.length;
		
		for ( int ictr = 0; ictr < count; ictr++ )
		{
			if ( signature[ictr] == null )
			{
				return true;
			}
		}

		return false;
	}

	/**
	  *	Build an array of names of the argument types. These types are biased toward
	  *	Java objects. That is, if an argument is of SQLType, then we map it to the
	  *	corresponding Java synonym class (e.g., SQLINT is mapped to 'java.lang.Integer').
	  *
	  *
	  *	@return	array of type names
	  *
	  * @exception StandardException		Thrown on error
	  */
	protected	String[]	getObjectSignature( )
		throws StandardException
	{
		int		count = signature.length;
		String	parmTypeNames[] = new String[ count ];

        TypeCompilerFactory tcf = (routineInfo == null ) ? null : getCompilerContext().getTypeCompilerFactory();

		for ( int i = 0; i < count; i++ ) { parmTypeNames[i] = getObjectTypeName( signature[ i ], tcf ); }

		return parmTypeNames;
	}

	/**
	 * Build an array of booleans denoting whether or not a given method
	 * parameter is a ?.
	 *
	 * @return array of booleans denoting wheter or not a given method
	 * parameter is a ?.
	 */
	protected boolean[] getIsParam()
	{
		if (methodParms == null)
		{
			return new boolean[0];
		}
		
		boolean[] isParam = new boolean[methodParms.length];

		for (int index = 0; index < methodParms.length; index++)
		{
			if (methodParms[index] instanceof SQLToJavaValueNode)
			{
				SQLToJavaValueNode stjvn = (SQLToJavaValueNode) methodParms[index];
				if (stjvn.value.requiresTypeFromContext())
				{
					isParam[index] = true;
				}
			}
		}

		return isParam;
	}

    @SuppressWarnings("fallthrough")
	static  String	getObjectTypeName( JSQLType jsqlType, TypeCompilerFactory tcf )
		throws StandardException
	{
		if ( jsqlType != null )
		{
			switch( jsqlType.getCategory() )
			{
			    case JSQLType.SQLTYPE: 

					TypeId	ctid = mapToTypeID( jsqlType );

					if ( ctid == null ) { return null; }
					else {
						// DB2 LUW does not support Java object types for SMALLINT, INTEGER, BIGINT, REAL, DOUBLE
						// and these are the only types that can map to a primitive or an object type according
						// to SQL part 13. So always map to the primitive type. We can not use the getPrimitiveSignature()
						// as it (incorrectly but historically always has) maps a DECIMAL to a double. 

						switch (ctid.getJDBCTypeId()) {
						case java.sql.Types.BOOLEAN:
						case java.sql.Types.SMALLINT:
						case java.sql.Types.INTEGER:
						case java.sql.Types.BIGINT:
						case java.sql.Types.REAL:
						case java.sql.Types.DOUBLE:
							if (tcf != null) {
								return tcf.getTypeCompiler( ctid ).getCorrespondingPrimitiveTypeName();
							}
							// fall through
						default:
							return ctid.getCorrespondingJavaTypeName();
						}
					}

		        case JSQLType.JAVA_CLASS: return jsqlType.getJavaClassName();

		        case JSQLType.JAVA_PRIMITIVE: return JSQLType.getPrimitiveName( jsqlType.getPrimitiveKind() );

		        default:

					if (SanityManager.DEBUG)
					{ SanityManager.THROWASSERT( "Unknown JSQLType: " + jsqlType ); }

			}
		}

		return "";
	}

	String[]	getPrimitiveSignature( boolean castToPrimitiveAsNecessary )
		throws StandardException
	{
		int					count = signature.length;
		String[] 			primParmTypeNames = new String[ count ];
        JSQLType            jsqlTyp;

		for (int i = 0; i < count; i++)
		{
            jsqlTyp = signature[ i ];

            if ( jsqlTyp == null ) { primParmTypeNames[i] = ""; }
			else
			{
                switch( jsqlTyp.getCategory() )
			    {
			        case JSQLType.SQLTYPE:

						if ((procedurePrimitiveArrayType != null)
							&& (i < procedurePrimitiveArrayType.length)
							&& (procedurePrimitiveArrayType[i] != null)) {

							primParmTypeNames[i] = procedurePrimitiveArrayType[i];

						} else {


                            TypeId  ctid = mapToTypeID( jsqlTyp );

							if ((ctid.isNumericTypeId() && !ctid.isDecimalTypeId()) || ctid.isBooleanTypeId())
							{
								TypeCompiler tc = getTypeCompiler(ctid);
								primParmTypeNames[i] = tc.getCorrespondingPrimitiveTypeName();
								if ( castToPrimitiveAsNecessary) { methodParms[i].castToPrimitive(true); }
							}
							else { primParmTypeNames[i] = ctid.getCorrespondingJavaTypeName(); }
						}

						break;

		            case JSQLType.JAVA_CLASS:

                        primParmTypeNames[i] = jsqlTyp.getJavaClassName();
						break;

		            case JSQLType.JAVA_PRIMITIVE:

                        primParmTypeNames[i] = JSQLType.getPrimitiveName(
                            jsqlTyp.getPrimitiveKind());

						if ( castToPrimitiveAsNecessary) { methodParms[i].castToPrimitive(true); }
						break;

		            default:

                        if (SanityManager.DEBUG) {
                            SanityManager.THROWASSERT(
                                "Unknown JSQLType: " + jsqlTyp );
                        }

				}	// end switch

			}		// end if

		}			// end for

		return primParmTypeNames;
	}

	/**
	 * Return the variant type for the underlying expression.
	 * The variant type can be:
	 *		VARIANT				- variant within a scan
	 *							  (non-static field access)
	 *		SCAN_INVARIANT		- invariant within a scan
	 *							  (column references from outer tables)
	 *		QUERY_INVARIANT		- invariant within the life of a query
	 *							  (constant expressions)
	 *
	 * @return	The variant type for the underlying expression.
	 */
    @Override
    int getOrderableVariantType() throws StandardException
	{
		// beetle 4880. We return the most variant type of the parameters. If no
		// params then query-invariant. This makes more sense, and we can evaluate
		// only once per query (good for performance) because method call could be
		// expensive.  And if we push down method qualifier to store, language
		// can pre-evaluate the method call.  This avoids letting store evaluate
		// the method while holding page latch, causing deadlock.

		return getVariantTypeOfParams();
	}

	private int getVariantTypeOfParams() throws StandardException
	{
		int variance = Qualifier.QUERY_INVARIANT;

		if (methodParms != null)
		{
			for (int parm = 0; parm < methodParms.length; parm++)
			{
				if (methodParms[parm] != null)
				{
					int paramVariantType =
						methodParms[parm].getOrderableVariantType();
					if (paramVariantType < variance)	//return the most variant type
						variance = paramVariantType;
				}
				else
				{
					variance = Qualifier.VARIANT;
				}
			}
		}

		return variance;
	}

    /**
     * Override method in ancestor.
     */
    @Override
    DataTypeDescriptor getDataType() throws StandardException
    {
        if ( routineInfo != null )
        {
            TypeDescriptor td = routineInfo.getReturnType();

            if ( td != null ) { return DataTypeDescriptor.getType( td ); }
        }

        return super.getDataType();
    }


	/////////////////////////////////////////////////////////////////////
	//
	//	ACCESSORS
	//
	/////////////////////////////////////////////////////////////////////
	/**
	 * Get the method parameters.
	 * 
	 * @return	The method parameters
	 */
    JavaValueNode[] getMethodParms()
	{
		return methodParms;
	}

	/**
	 * Accept the visitor for all visitable children of this node.
	 * 
	 * @param v the visitor
	 *
	 * @exception StandardException on error
	 */
    @Override
	void acceptChildren(Visitor v)
		throws StandardException
	{
		super.acceptChildren(v);

		for (int parm = 0; 
			!v.stopTraversal() && parm < methodParms.length; 
			parm++)
		{
			if (methodParms[parm] != null)
			{
				methodParms[parm] = (JavaValueNode)methodParms[parm].accept(v);
			}
		}
	}
}
