/*****************************************************************************
 *                                                                           *
 *  This file is part of the BeanShell Java Scripting distribution.          *
 *  Documentation and updates may be found at http://www.beanshell.org/      *
 *                                                                           *
 *  Sun Public License Notice:                                               *
 *                                                                           *
 *  The contents of this file are subject to the Sun Public License Version  *
 *  1.0 (the "License"); you may not use this file except in compliance with *
 *  the License. A copy of the License is available at http://www.sun.com    * 
 *                                                                           *
 *  The Original Code is BeanShell. The Initial Developer of the Original    *
 *  Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright     *
 *  (C) 2000.  All Rights Reserved.                                          *
 *                                                                           *
 *  GNU Public License Notice:                                               *
 *                                                                           *
 *  Alternatively, the contents of this file may be used under the terms of  *
 *  the GNU Lesser General Public License (the "LGPL"), in which case the    *
 *  provisions of LGPL are applicable instead of those above. If you wish to *
 *  allow use of your version of this file only under the  terms of the LGPL *
 *  and not to allow others to use your version of this file under the SPL,  *
 *  indicate your decision by deleting the provisions above and replace      *
 *  them with the notice and other provisions required by the LGPL.  If you  *
 *  do not delete the provisions above, a recipient may use your version of  *
 *  this file under either the SPL or the LGPL.                              *
 *                                                                           *
 *  Patrick Niemeyer (pat@pat.net)                                           *
 *  Author of Learning Java, O'Reilly & Associates                           *
 *  http://www.pat.net/~pat/                                                 *
 *                                                                           *
 *****************************************************************************/

package	bsh;

import java.util.Vector;

/**
	A ClassNameSpace represents the body a scripted class definition or
	scripted class instance.  In the case of the class definition it serves as
	the Class object and holds the class initializer method (called before the
	constructor).
	<p/>
	When serving as a class definition, only static members are visible and
	attempting to access an instance member will cause a "can't reach instance
	from static context" exception.
	<p/>
	When serving as a class instance static members are dissallowed (except
	by the initializer) and are ignored, allowing them to be seen in the class
	definition.
	<p/>
	Class instances also serve as the top scope of instance variables declared
	within the class (by the initializer).
*/
/*
	This would obviously be cleaner if we made a second subclass of NameSpace:
	ClassInstanceNameSpace.
*/
public class ClassNameSpace extends NameSpace 
{
	/** Java class for this class static body */
	Class javaClass;

	/** Java instance object for this class instance */
	Object javaObject;

    public ClassNameSpace( 
		NameSpace parent, String name, Class javaClass ) 
		throws EvalError
	{
		super( parent, name );
		this.javaClass = javaClass;
    }

	/**
		Instance constructor
	*/
    public ClassNameSpace( ClassNameSpace parent, Object javaObject ) 
		throws EvalError
	{
		super( parent, parent.getName() );
		this.javaObject = javaObject;
    }

	public This constructClassInstance( Object[] args, 
		Interpreter interpreter, CallStack callstack, SimpleNode callerInfo ) 
		throws EvalError
	{
		ClassNameSpace classNameSpace = this;
		String className = classNameSpace.getName();

		BshMethod classInitializer = null;
		try {
			classInitializer = 
				classNameSpace.getMethod( 
					BSHClassDeclaration.CLASSINITNAME, new Class[0] );
		} catch ( UtilEvalError e ) { // shouldn't happen
			throw e.toEvalError(
				"Error getting class initializer", callerInfo, callstack );
		}
		if ( classInitializer == null )
			throw new EvalError("Unable to find initializer for class.",
				callerInfo, callstack);

		// Get the (non-initializer) constructor, if any
		Class [] sig = Types.getTypes( args );
		BshMethod constructor = null;
		try {
			constructor = 
				classNameSpace.getMethod( className, sig );
		} catch ( UtilEvalError e ) { // shouldn't happen
			throw e.toEvalError(
				"Error getting constructor", callerInfo, callstack );
		}
		// No constructor found
		if ( constructor == null )
		{
			// args constructor not found
			if ( args.length > 0 )
				throw new EvalError("Constructor not found: "+
					StringUtil.methodString( className, sig ),
					callerInfo, callstack );
			else
			// no args constructor missing. Ok if no other constructors exist
			{
				BshMethod [] methods = classNameSpace.getMethods();
				for(int i=0; i<methods.length; i++)
					if ( methods[i].getName().equals( className ) )
						throw new EvalError(
							"Default (no args) constructor not found: "+
							StringUtil.methodString( className, sig ), 
							callerInfo, callstack );
			}
		}

		// Invoke the constructors

		// Recurse to handle superclasses 
		NameSpace superNameSpace = null; 
		if ( classNameSpace.getParent() instanceof ClassNameSpace
			&& ((ClassNameSpace)classNameSpace.getParent()).isClass()
		)
		{
			// Call the superClass's default constructor

			// Note: this isn't always right -
			// We need to allow super() in the constructor()

			This superInstance = 
				((ClassNameSpace)classNameSpace.getParent())
				.constructClassInstance( 
					new Object[0], interpreter, callstack, callerInfo );

			superNameSpace = superInstance.getNameSpace();
		}

		// Chain the instance namespaces
		NameSpace instanceNameSpace;
// REMOVE or put back
/*
		if ( superNameSpace != null )
			instanceNameSpace = new ClassNameSpace( 
				superNameSpace, className, INSTANCE );
		else
			instanceNameSpace = new ClassNameSpace( classNameSpace );
*/
		instanceNameSpace = null;

		callstack.push( instanceNameSpace );

		// Invoke the class initializer method
		try {
			classInitializer.invoke( 
				new Object[0], interpreter, callstack, callerInfo, 
				true/*overrideNameSpace*/ );
		} catch ( EvalError e ) {
			e.reThrow("Exception in default constructor: "+e);
		}

		// Call the specific constructor if any
		if ( constructor != null )
		{
			try {
				constructor.invoke( 
					args, interpreter, callstack, callerInfo, 
					true/*overrideNameSpace*/ );
			} catch ( EvalError e ) {
				e.reThrow("Exception in constructor: "+e);
			}
		}

		callstack.pop();
		
		// return the initialized object
		This instance = instanceNameSpace.getThis( interpreter );
		return instance;
	}

	public String toString() 
	{
		return "Scripted Class "
			+(isClassInstance() ?  "Instance " : "")
			+" : " +super.toString();
	}

	public boolean isClassInstance() { return javaObject != null; }

	public boolean isClass() { return javaClass != null; }

}

