/*
 * JavaInfoCmd.java
 *
 *	This file contains the Jacl implementation of the built-in java::info
 *	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: JavaInfoCmd.java,v 1.7 2006/04/13 07:36:50 mdejong Exp $
 */

package tcl.lang;
import tcl.lang.reflect.PkgInvoker;
import java.util.*;
import java.beans.*;
import java.lang.reflect.*;

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

class JavaInfoCmd implements Command {

static final private String validCmds[] = {
    "class",
    "baseclass",
    "dimensions",
    "events",
    "fields",
    "methods",
    "constructors",
    "properties",
    "superclass"
};

static final private int CLASS		= 0;
static final private int BASECLASS   	= 1;
static final private int DIMENSIONS   	= 2;
static final private int EVENTS   	= 3;
static final private int FIELDS   	= 4;
static final private int METHODS 	= 5;
static final private int CONSTRUCTORS	= 6;
static final private int PROPERTIES 	= 7;
static final private int SUPERCLASS	= 8;

static final private String propOpts[] = {"-type"};
static final private String methOpts[] = {"-type", "-static"};

static final int TYPE_OPT	= 0;
static final int STATIC_OPT	= 1;

/*
 *-----------------------------------------------------------------------------
 *
 * CmdProc --
 *
 *	This procedure is invoked to process the "java::info" 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 for info query.
    TclObject argv[])			// Argument list.
throws 
    TclException 			// Exceptions thrown as a result of bad
					//   user input.
{
    int lastArg = argv.length - 1;
    boolean statOpt = false;
    boolean typeOpt = false;
    TclObject resultListObj;
    Class c;

    if (argv.length < 2) {
	throw new TclNumArgsException(interp, 1, argv, 
		"option ?arg arg ...?");
    }

    int opt = TclIndex.get(interp, argv[1], validCmds, "option", 0);
    switch (opt) {
    case BASECLASS:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "objOrClass");
	}
	c = getClassFromObj(interp, argv[2]);
	if (c != null) {
	    interp.setResult(getBaseNameFromClass(c));
	}
	return;
    case CLASS:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "javaObj");
	}
	c = ReflectObject.getClass(interp, argv[2]);
	if (c != null) {
	    interp.setResult(getNameFromClass(c));
	}
	return;
    case DIMENSIONS:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "objOrClass");
	}
	c = getClassFromObj(interp, argv[2]);
	if (c == null) {
	    interp.setResult(0);
	} else {	  
	    interp.setResult(getNumDimsFromClass(c));
	}
	return;
    case EVENTS:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "javaObj");
	}
	c = getClassFromObj(interp, argv[2]);
	if (c == null) {
	    interp.resetResult();
	    return;
	}
 	if (!PkgInvoker.isAccessible(c)) {
	    JavaInvoke.notAccessibleError(interp, c);
	}
	lookup: {
	    BeanInfo beanInfo;

	    try {
		beanInfo = Introspector.getBeanInfo(c);
	    } catch (IntrospectionException e) {
		break lookup;
	    }
	    EventSetDescriptor events[] = beanInfo.getEventSetDescriptors();

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

	    TclObject list = TclList.newInstance();
	    for (int i = 0; i < events.length; i++) {
		TclList.append(interp, list, TclString.newInstance(
			getNameFromClass(events[i].getListenerType())));
	    }
	    interp.setResult(list);
	    return;
	}

	// The objOrClass doesn't support BeanInfo or it has no events.

	interp.resetResult();
	return;
    case FIELDS:
	if ((lastArg < 2) || (lastArg > 4)) {
	    throw new TclNumArgsException(interp, 2, argv, 
		    "?-type? ?-static? objOrClass");
	}
	for (int i= 2; i < lastArg; i++) {
	    opt = TclIndex.get(interp, argv[i], methOpts, "option", 0);
	    switch (opt) {
	    case STATIC_OPT:
		statOpt = true;
		break;
	    case TYPE_OPT:
		typeOpt = true;
		break;
	    }
	}
	c = getClassFromObj(interp, argv[lastArg]);
	if (c != null) {
	    if (!PkgInvoker.isAccessible(c)) {
	        JavaInvoke.notAccessibleError(interp, c);
	    }
	    resultListObj = getFieldInfoList(interp, c, statOpt, typeOpt);
	    interp.setResult(resultListObj);
	}
	return;
    case METHODS:
	if ((lastArg < 2) || (lastArg > 4)) {
	    throw new TclNumArgsException(interp, 2, argv, 
		    "?-type? ?-static? objOrClass");
	}
	for (int i= 2; i < lastArg; i++) {
	    opt = TclIndex.get(interp, argv[i], methOpts, "option", 0);
	    switch (opt) {
	    case STATIC_OPT:
		statOpt = true;
		break;
	    case TYPE_OPT:
		typeOpt = true;
		break;
	    }
	}
	c = getClassFromObj(interp, argv[lastArg]);
        if (c != null) {
	    if (!PkgInvoker.isAccessible(c)) {
	        JavaInvoke.notAccessibleError(interp, c);
	    }
	    resultListObj = getMethodInfoList(interp, c, statOpt, typeOpt);
	    interp.setResult(resultListObj);
        }
	return;
    case CONSTRUCTORS:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv, "objOrClass");
	}
	c = getClassFromObj(interp, argv[lastArg]);
        if (c != null) {
	    if (!PkgInvoker.isAccessible(c)) {
	        JavaInvoke.notAccessibleError(interp, c);
	    }
	    resultListObj = getConstructorInfoList(interp, c);
	    interp.setResult(resultListObj);
        }
	return;
    case PROPERTIES:
	if ((lastArg < 2) || (lastArg > 3)) {
	    throw new TclNumArgsException(interp, 2, argv, 
		    "?-type? objOrClass");
	}
	if (lastArg == 3) {
	    opt = TclIndex.get(interp, argv[2], propOpts, "option", 0);

	    // Since we just have one valid option, if the above call
	    // returns without an exception, we've got "-type" (or
	    // abreviations).

	    typeOpt = true;
	}
	c = getClassFromObj(interp, argv[lastArg]);
        if (c != null) {
	    if (!PkgInvoker.isAccessible(c)) {
	        JavaInvoke.notAccessibleError(interp, c);
	    }
	    resultListObj = getPropInfoList(interp, c, typeOpt);
	    interp.setResult(resultListObj);
        }
	return;
    case SUPERCLASS:
	if (argv.length != 3) {
	    throw new TclNumArgsException(interp, 2, argv,"objOrClass");
	}
	c = getClassFromObj(interp, argv[2]);

	interp.resetResult();
	if (c != null) {
	  c = c.getSuperclass();
	  
	  if (c != null) {
	    interp.setResult( getNameFromClass(c) );
	  }

	}
	return;
    }
}

/*
 *-----------------------------------------------------------------------------
 *
 * getClassFromObj --
 *
 *	Find the class associated with objOrClass.
 *
 * Results:
 *	Returns a Class.
 *
 * Side effects:
 *	Throws a Tcl exception if the objOrClass cannot be found.
 *
 *-----------------------------------------------------------------------------
 */
    
private static Class 
getClassFromObj(
    Interp interp,			// Current interpreter for info query.
    TclObject objOrClass) 		// Class or object for which the
					//   associated class is returned.
throws 
    TclException 			// Exceptions thrown as a result of bad
					//   user input.
{
    Class c;
    try {
        c = ReflectObject.getClass(interp, objOrClass);
    } catch (TclException e) {
	try {
	    c = ClassRep.get(interp, objOrClass);
	} catch (TclException e2) {
	    throw new TclException(interp, 
		    "unknown java class or object \"" + objOrClass + "\"");
	}
    }
    return c;
}

/*
 *-----------------------------------------------------------------------------
 *
 * gePropInfoList--
 *
 *	Find the list of properties.
 *
 * Results:
 *	Returns a TclObject list of properties.
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

private static TclObject
getPropInfoList(
    Interp interp, 			// Current interpreter for info query.
    Class c, 				// The class for which we return the
					//   properties.
    boolean typeOpt)			// Include prop-type info in result.
throws 
    TclException 			// Exceptions thrown as a result of bad
					//   user input.
{
    BeanInfo beaninfo;
    try {
	beaninfo = Introspector.getBeanInfo(c);
    } catch (IntrospectionException e) {
	throw new TclException(interp, e.toString());
    }

    PropertyDescriptor propDesc[] = null;
    propDesc = beaninfo.getPropertyDescriptors();

    TclObject resultListObj = TclList.newInstance();
    TclObject elementObj, pairObj;

    for (int i = 0; i < propDesc.length; i++) {
	// If the -type option was specified, create a list containing
	// the field's type and name.

	pairObj = TclList.newInstance();

	if (typeOpt) {
	    // The result of getPropertyType() may be "null" if this is an
	    // indexed property that does not support non-indexed access.
	    // For now, if the result is null, just don't add anything to the
	    // result.  This is as yet UNTESTED because I couldn't produce a
	    // case in which null was returned.
	    
	    elementObj = 
		TclString.newInstance(
                    getNameFromClass(propDesc[i].getPropertyType()));
	    if (elementObj != null) {
		TclList.append(interp, pairObj, elementObj);
	    }
	}
	elementObj = TclString.newInstance(propDesc[i].getName());
	TclList.append(interp, pairObj, elementObj);

	TclList.append(interp, resultListObj, pairObj);
    }
    return resultListObj;
}

/*
 *-----------------------------------------------------------------------------
 *
 * geFieldInfoList--
 *
 *	Find the list of fields.
 *
 * Results:
 *	Returns a TclObject list of field signatures.
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

private static TclObject
getFieldInfoList(
    Interp interp, 			// Current interpreter for info query.
    Class c, 				// The class for which we return the
					//   fields. 
    boolean statOpt, 			// Return only/no static field info.
    boolean typeOpt)			// Include feild-type info in result.
throws 
    TclException 			// Exceptions thrown as a result of bad
					//   user input.
{
    // Get the array of fields associated with that class.

    Field[] fieldArray = FieldSig.getAccessibleFields(c);

    // Check whether each field is static.  Based on -static option,
    // ignore the field or add it to the result list. 
	    
    TclObject resultListObj = TclList.newInstance();
    TclObject elementObj, sigObj, pairObj;
    Class declClass;

    for (int f = 0; f < fieldArray.length; ++f) {
	boolean isStatic = 
	    ((fieldArray[f].getModifiers() & Modifier.STATIC) > 0);
	if (isStatic == statOpt) {
	    // If the declaring class is the same as c, and the same field
	    // is also declared in c, then the signature is the name of the
	    // field.  Otherwise, the signature is a pair containing the field
	    // name and the declaring class name. 

	    sigObj = TclList.newInstance();

	    String fieldName = fieldArray[f].getName();
	    elementObj = TclString.newInstance(fieldName);
	    TclList.append(interp, sigObj, elementObj);
	    
	    declClass = fieldArray[f].getDeclaringClass();
	    if (!declClass.equals(c)) {
		for (int i = 0; i < fieldArray.length; ++i) {
		    if (i == f) {
			continue;
		    }
		    if (!fieldName.equals(fieldArray[i].getName())) {
			continue;
		    }
		    Class tmpClass = fieldArray[i].getDeclaringClass();
		    if (declClass.isAssignableFrom(tmpClass)) {
			elementObj = TclString.newInstance(
                            getNameFromClass(declClass));
			TclList.append(interp, sigObj, elementObj);
			break;
		    }			
		}
	    }
	    if (typeOpt) {
		// If -type was used, create a pair with the property type and
		// signature.  Append the pair to the result list.

		pairObj = TclList.newInstance();

		elementObj = TclString.newInstance(
		    getNameFromClass(fieldArray[f].getType()));
		TclList.append(interp, pairObj, elementObj);
		TclList.append(interp, pairObj, sigObj);
		TclList.append(interp, resultListObj, pairObj);
	    } else {
		// Append the signature object to the result list.
		
		TclList.append(interp, resultListObj, sigObj);		
	    }
	}
    }
    return resultListObj;
}

/*
 *-----------------------------------------------------------------------------
 *
 * getMethodInfoList--
 *
 *	Find the list of static or instance methods.
 *
 * Results:
 *	Returns a TclObject list of method signatures.
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

private static TclObject
getMethodInfoList(
    Interp interp, 			// Current interpreter for info query.
    Class c, 				// The class for which we return the
					//   methods. 
    boolean statOpt, 			// Return only/no static method info.
    boolean typeOpt)			// Include return-type info in result.
throws 
    TclException 			// Exceptions thrown as a result of bad
					//   user input.
{
    // Get the array of accessible static methods associated with the class,
    // otherwise get all the accessible non-static methods in the class,
    // its superclasses, and interfaces.

    Method[] methodArray;

    if (statOpt) {
	methodArray = FuncSig.getAccessibleStaticMethods(c);
    } else {
	methodArray = FuncSig.getAccessibleInstanceMethods(c);
    }
    
    TclObject resultListObj = TclList.newInstance();
    TclObject elementObj, sigObj;

    for (int m = 0; m < methodArray.length; ++m) {
	if (true) { // FIXME: left in to keep diff simple
	    // Create the signature.

	    sigObj = TclList.newInstance();

	    elementObj = TclString.newInstance(methodArray[m].getName());
	    TclList.append(interp, sigObj, elementObj);
	    
	    Class[] paramArray = methodArray[m].getParameterTypes();
	    for (int p = 0; p < paramArray.length; ++p) {
		elementObj = TclString.newInstance(
		    getNameFromClass(paramArray[p]));
		TclList.append(interp, sigObj, elementObj);
	    }

	    if (typeOpt) {
		// If -type was used, create a sublist with the
		// method type, signature and exception types.
		// Append the sublist the result list.

		TclObject sublist = TclList.newInstance();
		TclObject exceptions = TclList.newInstance();

		Class ex[] = methodArray[m].getExceptionTypes();
		for (int i = 0; i < ex.length; i++) {
		    TclList.append(interp, exceptions, TclString.newInstance(
			    getNameFromClass(ex[i])));
		}

		TclList.append(interp, sublist, TclString.newInstance(
			getNameFromClass(methodArray[m].getReturnType())));
		TclList.append(interp, sublist, sigObj);
		TclList.append(interp, sublist, exceptions);

		TclList.append(interp, resultListObj, sublist);
	    } else {
		// Append the signature object to the result list.
		
		TclList.append(interp, resultListObj, sigObj);		
	    }
	}
    }
    return resultListObj;
}

/*
 *-----------------------------------------------------------------------------
 *
 * geConstructorInfoList--
 *
 *	Find the list of constructors' signatures.
 *
 * Results:
 *	Returns a TclObject list of constructor names.
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */

private static TclObject 
getConstructorInfoList(
    Interp interp, 			// Current interpreter for info query.
    Class c) 				// The class for which we return the
					//   constructors. 
throws 
    TclException 			// Exceptions thrown as a result of
                                        // bad user input.
{
    // Get the array of constructors associated with that class.

    Constructor[] constructorArray = FuncSig.getAccessibleConstructors(c);

    TclObject resultListObj = TclList.newInstance();
    TclObject elementObj, sigObj;

    for (int m = 0; m < constructorArray.length; ++m) {
	// Create signature and append it to the result list.

	sigObj = TclList.newInstance();

	elementObj =
	    TclString.newInstance(constructorArray[m].getName());
	TclList.append(interp, sigObj, elementObj);

	Class[] paramArray = constructorArray[m].getParameterTypes();
	for (int p = 0; p < paramArray.length; ++p) {
	    elementObj = TclString.newInstance(
		getNameFromClass(paramArray[p]));
	    TclList.append(interp, sigObj, elementObj);
	}
	TclList.append(interp, resultListObj, sigObj);
    }
    return resultListObj;
}

/*
 *-----------------------------------------------------------------------------
 *
 * getNumDimsFromClass --
 *
 *      Return the number of dimension (# of nested arrays) for a type
 *
 * Results:
 *	Returns a non-negative integer.
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */
    
static int
getNumDimsFromClass(
    Class type)			// The class for which we return the name.
{
    int dim;
    for (dim = 0; type.isArray(); dim++) {
	type = type.getComponentType();
    }
    return dim;
}

/*
 *-----------------------------------------------------------------------------
 *
 * getNameFromClass --
 *
 *	Return the name of the class associated with "type".  If "type" is an
 *	array, for each dimension, append "[]" to the name of he base class. 
 *
 * Results:
 *	Returns a class name.
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */
    
static String
getNameFromClass(
    Class type)			// The class for which we return the name.
{
    StringBuffer name = new StringBuffer();

    while (type.isArray()) {
	name.append("[]");
	type = type.getComponentType();
    }
    String className = type.getName().replace('$', '.'); // For inner classes
    name.insert(0, className);
    return name.toString();
}

/*
 *-----------------------------------------------------------------------------
 *
 * getBaseNameFromClass --
 *
 *	Return the name of the base class associated with "type".
 *
 * Results:
 *	Returns a base class name.
 *
 * Side effects:
 *	None.
 *
 *-----------------------------------------------------------------------------
 */
    
private static String
getBaseNameFromClass(
    Class type)			// The class for which we return the name.
{
    while (type.isArray()) {
	type = type.getComponentType();
    }
    return type.getName().toString().replace('$', '.'); // For inner classes
}

} //end JavaInfoCmd

