/*
 * 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.9 2006/06/30 00:30:52 mdejong 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.

// FIXME: There is a problem here when mutliple interps could be making
// use of the same cachedInvokers table. If a name conflict were
// encountered, incorrect result would be the result.
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) {
	// Use the class loader that loaded the class
	// in question. The Class.getClassLoader()
	// API can return null to indicate the
	// bootstrap or system loader.

	ClassLoader cloader = cls.getClassLoader();

	try {
	    if (cloader != null) {
	        Class invCls = cloader.loadClass(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;
	}

// FIXME: should we store default pkg invoker in invoker table.
// Should we store all the possible package that do not have
// an invoker. How will we know if an earlier check failed ???
// This also needs to be tied into a common "cache" system that can
// be controlled by the user with some tcl commands.

	cachedInvokers.put(pkg, invoker);
    }

    return invoker;
}

// Return true if the passed in class uses the default invoker,
// meaning there is no custom invoker for the package.

public static boolean
usesDefaultInvoker(Class cls) {
    PkgInvoker invoker = getPkgInvoker(cls);
    return (invoker == defaultInvoker);
}

// Return true if the given class is accessible,
// meaning it is public or it is not private
// and we have an invoker for the package.

public static boolean
isAccessible(Class cls) {
    int mod = cls.getModifiers();
    if (Modifier.isPublic(mod))
        return true;
    if (Modifier.isPrivate(mod))
        return false;
    if (usesDefaultInvoker(cls))
        return false;
    return true;
}

// Return true if the given Method is accessible,
// meaning it is public or it is not private
// and we have an invoker for the package.

public static boolean
isAccessible(Method meth) {
    int mod = meth.getModifiers();
    if (Modifier.isPublic(mod))
        return true;
    if (Modifier.isPrivate(mod))
        return false;
    if (usesDefaultInvoker(meth.getDeclaringClass()))
        return false;
    return true;
}

// Return true if the given Constructor is accessible,
// meaning it is public or it is not private
// and we have an invoker for the package.

public static boolean
isAccessible(Constructor cons) {
    int mod = cons.getModifiers();
    if (Modifier.isPublic(mod))
        return true;
    if (Modifier.isPrivate(mod))
        return false;
    if (usesDefaultInvoker(cons.getDeclaringClass()))
        return false;
    return true;
}

// Return true if the given Field is accessible,
// meaning it is public or it is not private
// and we have an invoker for the package.

public static boolean
isAccessible(Field fld) {
    int mod = fld.getModifiers();
    if (Modifier.isPublic(mod))
        return true;
    if (Modifier.isPrivate(mod))
        return false;
    if (usesDefaultInvoker(fld.getDeclaringClass()))
        return false;
    return true;
}

} // end PkgInvoker

