/*****************************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one                *
 * or more contributor license agreements.  See the NOTICE file              *
 * distributed with this work for additional information                     *
 * regarding copyright ownership.  The ASF licenses this file                *
 * to you under the Apache License, Version 2.0 (the                         *
 * "License"); you may not use this file except in compliance                *
 * with the License.  You may obtain a copy of the License at                *
 *                                                                           *
 *     http://www.apache.org/licenses/LICENSE-2.0                            *
 *                                                                           *
 * Unless required by applicable law or agreed to in writing,                *
 * software distributed under the License is distributed on an               *
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY                    *
 * KIND, either express or implied.  See the License for the                 *
 * specific language governing permissions and limitations                   *
 * under the License.                                                        *
 *                                                                           *
 * This file is part of the BeanShell Java Scripting distribution.           *
 * Documentation and updates may be found at http://www.beanshell.org/       *
 * Patrick Niemeyer (pat@pat.net)                                            *
 * Author of Learning Java, O'Reilly & Associates                            *
 *                                                                           *
 *****************************************************************************/

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; }

}

