/*
 * JavaBindCmd.java --
 *
 *	Implements the java::bind command.
 *
 * 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: JavaBindCmd.java,v 1.1.1.1 1998/10/14 21:09:14 cvsadmin Exp $
 */

package tcl.lang;

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

/*
 * This class implements the built-in "java::bind" command in Tcl.
 */

class JavaBindCmd implements Command {

/*
 * The Bean Event Manager associated with the interp that owns this
 * BindCmd instance.
 */

BeanEventMgr eventMgr = null;

/*
 * Caches the BeanInfo for each Java class. The
 * Introspector.getBeanInfo class in JDK 1.2 returns new instances of
 * BeanInfo for each call.  That causes a lot of problems in Jacl,
 * which assumes that there is the BeanInfo (and EventSetDescriptor,
 * etc) associated with each class is always constant (i.e., always the
 * same object).
 *
 * This cache allows us to always use the same BeanInfo instance for each
 * Java class.
 */

private Hashtable beanInfoCache = new Hashtable();


/*
 *----------------------------------------------------------------------
 *
 * cmdProc --
 *
 *	This procedure is invoked as part of the Command interface to
 *	process the "java::bind" Tcl command.  See the user
 *	documentation for details on what it does.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

public void
cmdProc(
    Interp interp,		// Current interpreter.
    TclObject argv[])		// Argument list.
throws 
    TclException 		// A standard Tcl exception.
{
    if ((argv.length < 2) || (argv.length > 4)) {
	throw new TclNumArgsException(interp, 1, argv, 
		"javaObj ?eventName? ?command?");
    }

    ReflectObject robj = ReflectObject.getReflectObject(interp, argv[1]);
    Object obj = robj.javaObj;

    if (eventMgr == null) {
	eventMgr = BeanEventMgr.getBeanEventMgr(interp);
    }

    if (argv.length == 2) {
	/*
	 * Return the list of all events handled by this widget.
	 */

	interp.setResult(eventMgr.getHandledEvents(robj));
    } else {
	EventSetDescriptor eventDesc;
	Method method;

	Object arr[] = getEventMethod(interp, obj, argv[2].toString());
	eventDesc = (EventSetDescriptor)arr[0];

	if (!eventDesc.getListenerType().isInterface()) {
	    throw new TclException(interp, "Cannot handle event listener: " +
		    "listererType \"" + eventDesc.getListenerType() +
		    "\" is not an interface");
	}

	method = (Method)arr[1];

	if (argv.length == 3) {
	    /*
	     * Return the script for the given event.
	     */

	    TclObject script = eventMgr.getBinding(interp, robj, eventDesc,
		    method);

	    if (script != null) {
		interp.setResult(script);
	    } else {
		interp.resetResult();
	    }
	} else {
	    /*
	     * Set the script for the given event.
	     */

	    eventMgr.setBinding(interp, robj, eventDesc, method, argv[3]);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * getEventMethod --
 *
 *	Returns the EventSet and event listener method represented by
 *	a string name. The string name must be in one of the following
 *	formats:
 *		+ className.listenerMethod
 *		+ listenerMethod
 *	The first format will always work. The second and third format
 *	may cause an error if there is an ambiguity.
 *
 * Return value:
 *	If successful, returns an Object array of two elements. arr[0]
 *	is the EventSetDescriptor and arr[1] is the Method, as given
 *	by eventName.
 *	
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

Object[]
getEventMethod(
    Interp interp,		// Current interpreter.
    Object obj,			// The object whose event listener methods
				// are to be queried.
    String eventName)		// The string name of the event.
throws
   TclException			// If the method cannot be found, or if
				// eventName is ambiguous.
{
    EventSetDescriptor eventDesc = null;
    Method method = null;
    int dotPos, i;

    search: {
	BeanInfo beanInfo;

	try {
	    Class cls = obj.getClass();
	    beanInfo = (BeanInfo)beanInfoCache.get(cls);
	    if (beanInfo == null) {
		beanInfo = Introspector.getBeanInfo(cls);
		beanInfoCache.put(cls, beanInfo);
		
	    }
	} catch (IntrospectionException e) {
	    break search;
	}
	EventSetDescriptor events[] = beanInfo.getEventSetDescriptors();

	if (events == null) {
	    break search;
	}

	dotPos = eventName.lastIndexOf('.');
	if (dotPos == -1) {
	    /*
	     * the event string specifies only the event method. Must
	     * ensure that exactly one event interface has this
	     * method.
	     */

	    for (i = 0; i < events.length; i++) {
		Method methods[] = events[i].getListenerType().getMethods();
		for (int j=0; j < methods.length; j++) {
		    if (methods[j].getName().equals(eventName)) {
			if (method == null) {
			    method = methods[j];
			    eventDesc = events[i];
			} else {
			    throw new TclException(interp, 
				    "ambiguous event \"" + eventName + "\"");
			}
		    }
		}
	    }
	} else {
	    String evtCls = eventName.substring(0, dotPos);
	    String evtMethod = eventName.substring(dotPos + 1);

	    for (i = 0; i < events.length; i++) {
		Class lsnType = events[i].getListenerType();
		if (evtCls.equals(lsnType.getName())) {
		    eventDesc = events[i];
		    break;
		}
	    }

	    if (eventDesc == null) {
		break search;
	    }

	    Method methods[] = eventDesc.getListenerType().getMethods();

	    if (methods == null) {
		break search;
	    }
	    for (int j = 0; j < methods.length; j++) {
		if (methods[j].getName().equals(evtMethod)) {
		    method = methods[j];
		    break;
		}
	    }
	}

	if (method != null) {
	    Object arr[] = new Object[2];
	    arr[0] = eventDesc;
	    arr[1] = method;
	    return arr;
	}
    }

    throw new TclException(interp, "unknown event \"" + eventName + "\"");
}

} // end JavaBindCmd

