/*
 * FuncSig.java --
 *
 *	This class implements the internal representation of a Java
 *	method or constructor signature.
 *
 * Copyright (c) 1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and
 * redistribution of this file, and for a DISCLAIMER OF ALL
 * WARRANTIES.
 *
 * SCCS: @(#) FuncSig.java 1.5 98/02/11 14:53:55
 *
 */

package tcl.lang;

import tcl.lang.reflect.*;
import java.lang.reflect.*;
import java.util.*;

/*
 * This class implements the internal representation of a Java method
 * or constructor signature. Because methods and constructors are very
 * similar to each other, the operations on method signatures and
 * constructor signatures are limped in this class of "function
 * signature."
 */

class FuncSig extends InternalRep {

/*
 * The class that a method signature is used against. In the case of a
 * static method call by java::call, targetCls is given by the <class>
 * argument to java::call. In the case of an instance method call,
 * targetCls is the class of the instance. targetCls is used to test
 * the validity of a cached FuncSig internal rep for method
 * signatures.
 *
 * targetCls is not used for class signatures.
 */

Class targetCls;

/*
 * The PkgInvoker used to access the constructor or method.
 */

PkgInvoker pkgInvoker;

/*
 * The constructor or method given by the field signature. You need to
 * do apply the instanceof operator to determine whether it's a
 * Constructor or a Method.
 *
 * func may be a public, protected, package protected or private
 * member of the given class. Attempts to access func is subject to
 * the Java language access control rules. Public members can always
 * be accessed. Protected and package protected members can be
 * accessed only if a proper TclPkgInvoker class exists. Private
 * members can never be accessed.
 *
 * If the signature is a method signature and the specified method has
 * been overloaded, then func will point to the "most public" instance
 * of that method. The order of public-ness is ranked as the following
 * (a larger number means more public):
 *
 * RANK		     METHOD ACCESS TYPE      CLASS ACCESS TYPE	
 *   0			private			any
 *   1			package protected	protected
 *   1			protected		protected
 *   1			public			protected
 *   1			package protected	public
 *   1			protected		public
 *   2			public			public
 */

Object func;

/*
 * Stores the the all the declared methods of each Java class.
 */

static Hashtable allDeclMethTable = new Hashtable();


/*
 *----------------------------------------------------------------------
 *
 * FuncSig --
 *
 *	Creates a new FuncSig instance.
 *
 * Side effects:
 *	Member fields are initialized.
 *
 *----------------------------------------------------------------------
 */

FuncSig(
    Class cls,			// Initial value for targetCls.
    PkgInvoker p,		// Initial value for pkgInvoker.
    Object f)			// Initial value for func.
{
    targetCls = cls;
    pkgInvoker = p;
    func = f;
}

/*
 *----------------------------------------------------------------------
 *
 * duplicate --
 *
 *	Make a copy of an object's internal representation.
 *
 * Results:
 *	Returns a newly allocated instance of the appropriate type.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

protected InternalRep
duplicate()
{
    return new FuncSig(targetCls, pkgInvoker, func);
}

/*
 *----------------------------------------------------------------------
 *
 * get --
 *
 *	Returns the FuncSig internal representation of the constructor
 *	or method that matches with the signature and the parameters.
 *
 * Results:
 *	The FuncSig given by the signature.
 *
 * Side effects:
 *	When successful, the internalRep of the signature object is
 *	converted to FuncSig.
 *
 *----------------------------------------------------------------------
 */

static FuncSig
get(
    Interp interp,		// Current interpreter.	
    Class cls,			// If null, we are looking for a constructor
				// in signature. If non-null, we are looking
				// for a method of this class in signature.
    TclObject signature,	// Method/constructor signature.
    TclObject argv[],		// Arguments.
    int startIdx,		// Index of the first argument in argv[] to
				// pass to the constructor.
    int count)			// Number of arguments to pass to the
				// constructor.
throws
    TclException
{
    boolean isConstructor = (cls == null);

    InternalRep rep = signature.getInternalRep();

    /*
     * If a valid FuncSig internal rep is already cached, return it
     * right away.
     */

    if (rep instanceof FuncSig) {
	FuncSig tmp = (FuncSig)rep;
	Object func = tmp.func;

	if (isConstructor) {
	    if ((func instanceof Constructor)
		    && (((Constructor)func).getParameterTypes().length
			    == count)) {
		return tmp;
	    }
	} else {
	    if ((func instanceof Method) && (tmp.targetCls == cls)
		    && (((Method)func).getParameterTypes().length == count)) {
		return tmp;
	    }
	}
    }

    /*
     * Look up the constructor or method using the string rep of the
     * signature object.
     */

    Object match = null;
    int sigLength = TclList.getLength(interp, signature);
    String methodName = null;
    boolean foundSameName = false;

    if (sigLength == 0) {
	throw new TclException(interp, "bad signature \"" + signature + "\"");
    }

    if (isConstructor) {
	cls = JavaInvoke.getClassByName(interp, TclList.index(interp,
		signature, 0).toString());
    } else {
	methodName = TclList.index(interp, signature, 0).toString();
    }

    if ((sigLength > 1) || (sigLength == 1 && count == 0)) {
	/*
	 * We come to here if one of the following two cases in true:
	 *
	 *     [1] (sigLength > 1): A signature has been given.
	 *     [2] (sigLength == 1 && count == 0): A signature of no
	 *         parameters is implied.
	 *
	 * In both cases, we search for a method that matches exactly
	 * with the signature.
	 */

	int sigNumArgs = sigLength - 1;
	Class paramTypes[] = new Class[sigNumArgs];

	for (int i = 0; i < sigNumArgs; i++) {
	    String clsName = TclList.index(interp, signature, i+1).toString();
	    paramTypes[i] = JavaInvoke.getClassByName(interp, clsName);
	}

	if (isConstructor) {
	    try {
		match = cls.getDeclaredConstructor(paramTypes);
	    } catch (NoSuchMethodException e) {
		/*
		 * The error will be reported at the end of this method.
		 */

		match = null;
	    }
	} else {
	    Method methods[] = getAllDeclaredMethods(cls);

	    for (int i = 0; i < methods.length; i++) {
		if (!(methodName.equals(methods[i].getName()))) {
		    continue;
		}

		foundSameName = true;

		Class pt[] = methods[i].getParameterTypes();
		if (pt.length != paramTypes.length) {
		    continue;
		}

		boolean good = true;
		for (int j = 0; j < pt.length; j ++) {
		    if (!(pt[j].equals(paramTypes[j]))) {
			good = false;
			break;
		    }
		}
		if (good) {
		    match = methods[i];
		    break;
		}
	    }
	}
    } else {
	/*
	 * No argument types has been specified. Use number of args to
	 * match with the functions. Throws execption if more than
	 * one function has the same number of args.
	 */

	Object funcs[];
	if (isConstructor) {
	    funcs = cls.getDeclaredConstructors();
	} else {
	    funcs = getAllDeclaredMethods(cls);
	}
	Class paramTypes[];

	for (int i = 0; i < funcs.length; i++) {
	    if (isConstructor) {
		paramTypes = ((Constructor)funcs[i]).getParameterTypes();
	    } else {
		Method method = ((Method)funcs[i]);
		if (!methodName.equals(method.getName())) {
		    continue;
		}
		foundSameName = true;

		paramTypes = method.getParameterTypes();
	    }

	    /*
	     * An method/constructor matches only if its name is an exact
	     * match and the number of args are the same.
	     *
	     * (ToDo): add more "guessing" rules depending on the forms
	     * types of the arguments.
	     */

	    if (paramTypes.length == count) {
		if (match == null) {
		    match = funcs[i];
		} else {
		    throw new TclException(interp, "ambiguous " + 
			    (isConstructor ? "constructor" : "method") +
			    " signature \"" + signature + "\"");
		}
	    }
	}
    }

    if (match == null) {
	if (isConstructor) {
	    if (sigLength > 1) {
		throw new TclException(interp, "no such constructor \"" +
		        signature + "\"");
	    } else {
		throw new TclException(interp, "can't find constructor with "
			+ count + " argument(s) for class \"" + cls.getName()
			+ "\"");
	    }
	} else {
	    String msg;
	    if ((sigLength > 1) || (!foundSameName)) {
		throw new TclException(interp,
			"no such method \"" + signature + "\" in class " +
	    		cls.getName());
	    } else {
		throw new TclException(interp,
			"can't find method \"" + signature + "\" with "
			+ count + " argument(s) for class \"" + cls.getName()
			+ "\"");
	    }
	}
    }

    FuncSig sig = new FuncSig(cls, PkgInvoker.getPkgInvoker(cls), match);
    signature.setInternalRep(sig);

    return sig;
}

/*
 *----------------------------------------------------------------------
 *
 * getAllDeclaredMethods --
 *
 *	Returns all the methods declared by the class or superclasses
 *	of the class.
 *
 * Results:
 *	An array of all the methods declared by the class of
 *	superclasses of this class. If overloaded methods, only the
 *	"most public" instance of that method is included in the
 *	array. See comments above the "func" member variable for more
 *	details.
 *
 * Side effects:
 *	The array of methods are saved in a hashtable for faster access
 *	in the future.
 *
 *----------------------------------------------------------------------
 */

static Method[]
getAllDeclaredMethods(
    Class cls)				// The class to query.
{
    Method methods[] = (Method[])allDeclMethTable.get(cls);
    if (methods != null) {
	return methods;
    }

    Vector vec = new Vector();

    for (Class c = cls; c != null; c = c.getSuperclass()) {
	mergeMethods(c, c.getDeclaredMethods(), vec);

	Class interfaces[] = c.getInterfaces();
	for (int i = 0; i < interfaces.length; i++) {
	    mergeMethods(interfaces[i], interfaces[i].getMethods(), vec);
	}
    }

    methods = new Method[vec.size()];
    vec.copyInto(methods);
    allDeclMethTable.put(cls, methods);

    return methods;
}

/*
 *----------------------------------------------------------------------
 *
 * mergeMethods --
 *
 *	Add the methods declared by a super-class or an interface to
 *	the list of declared methods of a class.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Elements of methods[] are added to vec. If an instance of
 *	an overloaded method is already in vec, it will be replaced
 *	by a new instance only if the new instance has a higher rank.
 *
 *----------------------------------------------------------------------
 */

static void 
mergeMethods(
    Class c,
    Method methods[],
    Vector vec)
{
    for (int i = 0; i < methods.length; i++) {
	boolean sameSigExists = false;
	Method newMeth = methods[i];

	for (int j=0; j<vec.size(); j++) {
	    Method oldMeth = (Method)vec.elementAt(j);

	    if (methodSigEqual(oldMeth, newMeth)) {
		sameSigExists = true;

		Class oldCls = oldMeth.getDeclaringClass();
		int newRank = getMethodRank(c, newMeth);
		int oldRank = getMethodRank(oldCls, oldMeth);

		if (newRank > oldRank) {
		    vec.setElementAt(newMeth, j);
		}
		break;
	    }
	}
	    
	if (!sameSigExists) {
	    /*
	     * We copy a method into the vector only if no method
	     * with the same signature is already in the
	     * vector. Otherwise the matching routine in the get()
	     * procedure may run into "ambiguous method signature"
	     * errors when it sees instances of overloaded
	     * methods.
	     */

	    vec.addElement(newMeth);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * methodSigEqual --
 *
 *	Returns whether the two methods have the same signature.
 *
 * Results:
 *	True if the method names and arguments are the same. False
 *	otherwise
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static boolean
methodSigEqual(
    Method method1,
    Method method2)
{
    if (!method1.getName().equals(method2.getName())) {
	return false;
    }

    Class param1[] = method1.getParameterTypes();
    Class param2[] = method2.getParameterTypes();

    if (param1.length != param2.length) {
	return false;
    }


    for (int i=0; i<param1.length; i++) {
	if (param1[i] != param2[i]) {
	    return false;
	}
    }

    return true;
}

/*
 *----------------------------------------------------------------------
 *
 * getMethodRank --
 *
 *	Returns the rank of "public-ness" of the method. See comments
 *	above the "func" member variable for more details on public-ness
 *	ranking.
 *
 * Results:
 *	The rank of "public-ness" of the method.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
getMethodRank(
    Class declaringCls,		// The class that declares the method.
    Method method)		// Return the rank of this method.
{
    int methMod = method.getModifiers();

    if (Modifier.isPrivate(methMod)) {
	return 0;
    }

    int clsMod = declaringCls.getModifiers();

    if (Modifier.isPublic(methMod) && Modifier.isPublic(clsMod)) {
	return 2;
    }

    return 0;
}

} // end FuncSig.
