/*
 * PkgInvoker --
 *
 *	This class is used for the java::* commands to gain access to
 *	package protected and protected members of a Java package.
 *
 * 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.
 *
 * RCS: @(#) $Id: PkgInvoker.java,v 1.1.1.1 1998/10/14 21:09:15 cvsadmin Exp $
 *
 */

package tcl.lang.reflect;

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

/*
 * This class is used for the java::* commands to gain access to
 * package protected and protected members of a Java package. 
 *
 * Normally, the java::* command can only access public members of
 * public classes inside any package. With the help of the PkgInvoker
 * class, the java::* command can access any member (constructor,
 * method, field or property) of any class in a package as long as the
 * member is not explicitly declared "private"
 *
 * The ability for Tcl to access protected members is desirable when
 * we use Tcl to perform "white-box" testing on the methods of a Java
 * package.
 *
 * To grant Tcl access to protected members of a package, do the
 * following:
 *
 *	+ Create a subclass of PkgInvoker, give it the name
 *	  "TclPkgInvoker", declare it public and place it inside the
 *	  said package.
 *
 *	+ Give your TclPkgInvoker class a public constructor with
 *	  no arguments.
 *
 *	+ Cut-and-paste the definitions of the following functions into
 *	  TclPkgInvoker class: invokeMethod, invokeConstructor,
 *	  getField and setField.
 *
 * An example of using PkgInvoker can be found in the directory
 * src/tests/pkg1 and the file tests/common/PkgInvoker.test
 *
 */

public class PkgInvoker {

/*
 * PkgInvokers of the packages that we have already visited are stored
 * in this hashtable. They key is the String name of a package.
 */

static Hashtable cachedInvokers = new Hashtable();

/*
 * This is the default invoker to use if a package doesn't include a
 * proper TclPkgInvoker class. This means only the public members
 * of the public classes of that package can be accessed diretly
 * from Tcl.
 */

static PkgInvoker defaultInvoker = new PkgInvoker();

/*
 *----------------------------------------------------------------------
 *
 * invokeConstructor --
 *
 *	Invoke the given constructor with the arguments.
 *
 * Results:
 *	The new object instance returned by the constructor.
 *
 * Side effects:
 *	The constructor may have arbitraty side effects.
 *
 *----------------------------------------------------------------------
 */

public Object 
invokeConstructor(
    Constructor constructor,	// The constructor to invoke.
    Object args[])		// Arguments for the constructor.
throws
    InstantiationException,	// Standard exceptions thrown by
    IllegalAccessException,	// Constructor.newInstance.
    IllegalArgumentException,
    InvocationTargetException
{
    return constructor.newInstance(args);
}

/*
 *----------------------------------------------------------------------
 *
 * invokeMethod --
 *
 *	Invoke the given method of the obj with the arguments.
 *
 * Results:
 *	The value returned by the method.
 *
 * Side effects:
 *	The method may have arbitraty side effects.
 *
 *----------------------------------------------------------------------
 */

public Object
invokeMethod(
    Method method,		// The method to invoke.
    Object obj,			// The object associated with the method.
				// May be null if the method is static.
    Object args[])		// The arguments for the method.
throws
    IllegalAccessException,	// Standard exceptions throw by Method.Invoke.
    IllegalArgumentException,
    InvocationTargetException
{
    return method.invoke(obj, args);
}

/*
 *----------------------------------------------------------------------
 *
 * getField --
 *
 *	Query the value of the given field.
 *
 * Results:
 *	The value of the field.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */


public Object
getField(
    Field field,		// The field to query.
    Object obj)			// The object that owns the field. May be
				// null for static fields.
throws
    IllegalArgumentException,	// Standard exceptions thrown by Field.get().
    IllegalAccessException
{
    return field.get(obj);
}

/*
 *----------------------------------------------------------------------
 *
 * setField --
 *
 *	Modify the value of the given field.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When successful, the field is modified to be the new value.
 *
 *----------------------------------------------------------------------
 */

public void
setField(
    Field field,		// The field to modify.
    Object obj,			// The object that owns the field. May be
				// null for static fields.
    Object value)		// New value for the field.
throws
    IllegalArgumentException,	// Standard exceptions thrown by Field.set().
    IllegalAccessException
{
    field.set(obj, value);
}

/*
 *----------------------------------------------------------------------
 *
 * getPkgInvoker --
 *
 *	Returns the PkgInvoker for the package that includes the given
 *	class.
 *
 * Results:
 *	An instance of the PkgInvoker which is included in the package,
 *	or defaultInvoker if the package doesn't include a proper
 *	PkgInvoker.
 *
 * Side effects:
 *	The returned value is also stored in a hashtable for faster
 *	access in the future.
 *
 *----------------------------------------------------------------------
 */

public static final PkgInvoker
getPkgInvoker(
    Class cls)			// Query the PkgInvoker of the package
				// that owns this class.
{
    String clsName = cls.getName();
    int index = clsName.lastIndexOf('.');
    String pkg;

    if (index == -1) {
	pkg = "";
    } else {
	pkg = clsName.substring(0, index);
    }

    PkgInvoker invoker = (PkgInvoker)cachedInvokers.get(pkg);
    if (invoker == null) {
	try {
	    Class invCls = Class.forName(pkg + ".TclPkgInvoker");
	    invoker = (PkgInvoker)(invCls.newInstance());
	} catch (Exception e) {
	    /*
	     * The package doesn't include a PkgInvoker class. We use
	     * the default invoker, which means we can't invoke
	     * any of the protected members inside this package.
	     */

	    invoker = defaultInvoker;
	}

	if (invoker == null) {
	    invoker = defaultInvoker;
	}

	cachedInvokers.put(pkg, invoker);
    }

    return invoker;
}

} // end PkgInvoker

