/*
 * Copyright 2004-2011 The Apache Software Foundation.
 *
 * Licensed 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.
 */

package org.apache.bsf;

import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.MissingResourceException;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;

import org.apache.bsf.util.CodeBuffer;
import org.apache.bsf.util.ObjectRegistry;

    // org.apache.commons.logging is delegated to "org.apache.bsf.BSF_Log[Factory]"
// import org.apache.commons.logging.Log;
// import org.apache.commons.logging.LogFactory;

/**
 * This class is the entry point to the bean scripting framework. An
 * application wishing to integrate scripting to a Java app would
 * place an instance of a BSFManager in their code and use its services
 * to register the beans they want to make available for scripting,
 * load scripting engines, and run scripts.
 * <p>
 * BSFManager serves as the registry of available scripting engines
 * as well. Loading and unloading of scripting engines is
 * supported as well. Each BSFManager loads one engine per language.
 * Several BSFManagers can be created per JVM.
 *
 * @author   Sanjiva Weerawarana
 * @author   Matthew J. Duftler
 * @author   Sam Ruby
 * @author   Olivier Gruber (added original debugging support)
 * @author   Don Schwarz (added support for registering languages dynamically)
 * @author   Rony G. Flatscher (added BSF_Log[Factory] to allow BSF to run without org.apache.commons.logging present)
 */

/*  changed:
    2013-06-30: ---rgf, update the version's date to reflect that the source code has changed
    2013-06-29: ---rgf, if terminating engines, make sure to remove engine as PropertyChange listener
    2011-01-08: ---rgf, static constructor: fixed logic error in fallback code for getResources()
    2009-09-10: ---rgf, context class loader may not be set, account for it

    2007-09-17: ---rgf, some Java hosts do not set the Thread's context class loader and
                       load BSF with a customized ClassLoader!
                       Resolution:
                       - use Thread context ClassLoader, if resource or class to
                         load not found, then
                       - use the BSFManager's defining ClassLoader instead, if it is
                         different to the context ClassLoader

    2007-01-28: ---rgf, fixed Class.forName() to use the context class loader instead; oversaw this the last time
*/

public class BSFManager {
    // version string is in the form "abc.yyyymmdd" where
    // "abc" represents a dewey decimal number (three levels, each between 0 and 9),
    // and "yyyy" a four digit year, "mm" a two digit month, "dd" a two digit day.
    //
    // Example: "245.20090910" stands for: BSF version "2.4.5" as of "2009-09-10"
    protected static String version="245.20130629";

    // table of registered scripting engines
    protected static Hashtable registeredEngines = new Hashtable();

    // mapping of file extensions to languages
    protected static Hashtable extn2Lang = new Hashtable();

    // get the defined CL (ClassLoader which got used to define this class object) // rgf, 20070917
    protected static ClassLoader definedClassLoader;
/*
    protected static ClassLoader appClassLoader;        // application/system class loader
    protected static ClassLoader extClassLoader;        // extension (option) class loader
*/

    /** Returns the defined ClassLoader (the ClassLoader that got used to define the
     *  org.apache.bsf.BSFManager class object).
     *  @return the defined ClassLoader instance
     */
    public static ClassLoader getDefinedClassLoader()  // rgf, 20070917
    {
        return definedClassLoader;
    }


    // table of scripting engine instances created by this manager.
    // only one instance of a given language engine is created by a single
    // manager instance.
    protected Hashtable loadedEngines = new Hashtable();

    // table of registered beans for use by scripting engines.
    protected ObjectRegistry objectRegistry = new ObjectRegistry();

    // prop change support containing loaded engines to inform when any
    // of my interesting properties change
    protected PropertyChangeSupport pcs;

/* rgf (20070917): wrong assumption; context ClassLoader needs to be explicitly
                   requested before usage as BSF could be deployed with different
                   context ClassLoaders on different threads!
*/

    // the class loader to use if a class loader is needed. Default is
    // he who loaded me (which may be null in which case its Class.forName).
    protected ClassLoader classLoader = getClass().getClassLoader();
    // rgf, 20070917, reset to original// protected ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // rgf, 2006-01-05

    // temporary directory to use to dump temporary files into. Note that
    // if class files are dropped here then unless this dir is in the
    // classpath or unless the classloader knows to look here, the classes
    // will not be found.
    protected String tempDir = ".";

    // classpath used by those that need a classpath
    protected String classPath;

    // stores BSFDeclaredBeans representing objects
    // introduced by a client of BSFManager
    protected Vector declaredBeans = new Vector();

    // private Log logger = LogFactory.getLog(this.getClass().getName());
    private BSF_Log logger = null;

    //////////////////////////////////////////////////////////////////////
    //
    // pre-register engines that BSF supports off the shelf
    //
    //////////////////////////////////////////////////////////////////////

    static {
        String strInfo="org.apache.bsf.BSFManager.dumpEnvironment() [from static{}]";
        try {
            definedClassLoader=BSFManager.class.getClassLoader();   // get defining ClassLoader

/*
            RGFInfo.newSection(strInfo);
            RGFInfo.dumpClassLoaderInfos(BSFManager.class, "org.apache.bsf.BSFManager");
            RGFInfo.append("BSFManager.staticBlock, registering BSF scripting engines:\n");
*/

            // Enumeration e = BSFManager.class.getClassLoader().getResources("org/apache/bsf/Languages.properties");
            // use the Thread's context class loader to locate the resources
            String resourceName="org/apache/bsf/Languages.properties";

            // ---rgf, 2009-09-10
/*
            Thread.currentThread().getContextClassLoader().getResources(resourceName);

            if (!e.hasMoreElements()) // 20070917: if no resources (BSF engines!), then use defCL
            {
//                RGFInfo.append("==> no BSF engines found from context ClassLoader, now using defCL !\n");
                e=definedClassLoader.getResources(resourceName);
            }
*/

            Enumeration e = null;
            ClassLoader tccl=Thread.currentThread().getContextClassLoader();    // try to get the context class loader
            if (tccl!=null)                         // no context class loader available!
            {
                e=tccl.getResources(resourceName);
            }
            else  // fallback, rgf, 2011
            {
                e=definedClassLoader.getResources(resourceName);
		Thread.currentThread().setContextClassLoader(definedClassLoader); // rgf, 2011-01-08
            }

/*
            if (tccl==null | !e.hasMoreElements())  // either no CCL or nor elements, use this class' resources
            {
//                RGFInfo.append("==> no BSF engines found from context ClassLoader, now using defCL !\n");
                e=definedClassLoader.getResources(resourceName);
            }
*/

            while (e.hasMoreElements()) {
                URL url = (URL)e.nextElement();
                InputStream is = url.openStream();

                Properties p = new Properties();
                p.load(is);

                for (Enumeration keys = p.propertyNames(); keys.hasMoreElements();) {

                    String key = (String) keys.nextElement();
                    String value = p.getProperty(key);
                    String className = value.substring(0, value.indexOf(","));


                    // get the extensions for this language
                    String exts = value.substring(value.indexOf(",")+1, value.length());
                    StringTokenizer st = new StringTokenizer(exts, "|");
                    String[] extensions = new String[st.countTokens()];

//                    RGFInfo.append("\tregistering BSF engine: key=["+key+"]\tclassName=["+className+"] nr. of extensions=["+extensions.length+"\n");
                    for (int i = 0; st.hasMoreTokens(); i++) {
                        extensions[i] = ((String) st.nextToken()).trim();
//                        RGFInfo.append("\t\t--->extension=["+extensions[i]+"]\n");
                    }

                    registerScriptingEngine(key, className, extensions);
                }
            }
        } catch (IOException ex) {

            ex.printStackTrace();
            System.err.println("Error reading Languages file " + ex);
//            RGFInfo.append("\t*ERROR* reading Language file ["+ex+"]\n");
        } catch (NoSuchElementException nsee) {

            nsee.printStackTrace();
            System.err.println("Syntax error in Languages resource bundle");
//            RGFInfo.append("\t*ERROR* Syntax error in Languages resourcebundle ["+nsee+"]\n");
        } catch (MissingResourceException mre) {

            mre.printStackTrace();
            System.err.println("Initialization error: " + mre.toString());
//            RGFInfo.append("\t*ERROR* initializing ["+mre+"]\n");
        }
        finally
        {
//            RGFInfo.dumpEnvironment();
//            RGFInfo.endSection(strInfo);
        }
    }

    public BSFManager() {
        pcs = new PropertyChangeSupport(this);
            // handle logger
        // logger = LogFactory.getLog(this.getClass().getName());
        logger = BSF_LogFactory.getLog(this.getClass().getName());
    }


   /** Returns the version string of BSF.
     *
     * @return version string in the form &quot;abc.yyyymmdd&quot; where
       &quot;abc&quot; represents a dewey decimal number (three levels, each between 0 and 9), and
       &quot;yyyy&quot; a four digit year, &quot;mm&quot; a two digit month,
       &quot;dd&quot; a two digit day.
    *
       <br>Example: &quot;<code>244.20080623</code>&quot;
       stands for: BSF version <code>2.4.5</code> as of <code>2009-09-10</code>.
    *
    *
     * @since 2006-01-17
     */
    public static String getVersion() {

        return version;
    }

    /**
     * Apply the given anonymous function of the given language to the given
     * parameters and return the resulting value.
     *
     * @param lang language identifier
     * @param source (context info) the source of this expression
     (e.g., filename)
     * @param lineNo (context info) the line number in source for expr
     * @param columnNo (context info) the column number in source for expr
     * @param funcBody the multi-line, value returning script to evaluate
     * @param paramNames the names of the parameters above assumes
     * @param arguments values of the above parameters
     *
     * @exception BSFException if anything goes wrong while running the script
     */
    public Object apply(String lang,
                        String source,
                        int lineNo,
                        int columnNo,
                        Object funcBody,
                        Vector paramNames,
                        Vector arguments)
        throws BSFException {
    	logger.debug("BSFManager:apply");

    	final BSFEngine e = loadScriptingEngine(lang);
        final String sourcef = source;
        final int lineNof = lineNo, columnNof = columnNo;
        final Object funcBodyf = funcBody;
        final Vector paramNamesf = paramNames;
        final Vector argumentsf = arguments;
        Object result = null;

        try {
            final Object resultf =
                AccessController.doPrivileged(new PrivilegedExceptionAction() {
                        public Object run() throws Exception {
                            return e.apply(sourcef, lineNof, columnNof,
                                           funcBodyf, paramNamesf, argumentsf);
                        }
                    });
            result = resultf;
        } catch (PrivilegedActionException prive) {

        	logger.error("[BSFManager] Exception: ", prive);
            throw (BSFException) prive.getException();
        }

        return result;
    }

    /**
     * Compile the application of the given anonymous function of the given
     * language to the given parameters into the given <tt>CodeBuffer</tt>.
     *
     * @param lang language identifier
     * @param source (context info) the source of this expression
     (e.g., filename)
     * @param lineNo (context info) the line number in source for expr
     * @param columnNo (context info) the column number in source for expr
     * @param funcBody the multi-line, value returning script to evaluate
     * @param paramNames the names of the parameters above assumes
     * @param arguments values of the above parameters
     * @param cb       code buffer to compile into
     *
     * @exception BSFException if anything goes wrong while running the script
     */
    public void compileApply(String lang,
                             String source,
                             int lineNo,
                             int columnNo,
                             Object funcBody,
                             Vector paramNames,
                             Vector arguments,
                             CodeBuffer cb)
        throws BSFException {
    	logger.debug("BSFManager:compileApply");

        final BSFEngine e = loadScriptingEngine(lang);
        final String sourcef = source;
        final int lineNof = lineNo, columnNof = columnNo;
        final Object funcBodyf = funcBody;
        final Vector paramNamesf = paramNames;
        final Vector argumentsf = arguments;
        final CodeBuffer cbf = cb;

        try {
            AccessController.doPrivileged(new PrivilegedExceptionAction() {
                    public Object run() throws Exception {
                        e.compileApply(sourcef, lineNof, columnNof,
                                       funcBodyf, paramNamesf,
                                       argumentsf, cbf);
                        return null;
                    }
                });
        } catch (PrivilegedActionException prive) {

        	logger.error("[BSFManager] Exception :", prive);
            throw (BSFException) prive.getException();
        }
    }

    /**
     * Compile the given expression of the given language into the given
     * <tt>CodeBuffer</tt>.
     *
     * @param lang     language identifier
     * @param source   (context info) the source of this expression
     (e.g., filename)
     * @param lineNo   (context info) the line number in source for expr
     * @param columnNo (context info) the column number in source for expr
     * @param expr     the expression to compile
     * @param cb       code buffer to compile into
     *
     * @exception BSFException if any error while compiling the expression
     */
    public void compileExpr(String lang,
                            String source,
                            int lineNo,
                            int columnNo,
                            Object expr,
                            CodeBuffer cb)
        throws BSFException {
    	logger.debug("BSFManager:compileExpr");

    	final BSFEngine e = loadScriptingEngine(lang);
        final String sourcef = source;
        final int lineNof = lineNo, columnNof = columnNo;
        final Object exprf = expr;
        final CodeBuffer cbf = cb;

        try {
            AccessController.doPrivileged(new PrivilegedExceptionAction() {
                    public Object run() throws Exception {
                        e.compileExpr(sourcef, lineNof, columnNof, exprf, cbf);
                        return null;
                    }
                });
        } catch (PrivilegedActionException prive) {

        	logger.error("[BSFManager] Exception :", prive);
            throw (BSFException) prive.getException();
        }
    }

    /**
     * Compile the given script of the given language into the given
     * <tt>CodeBuffer</tt>.
     *
     * @param lang     language identifier
     * @param source   (context info) the source of this script
     (e.g., filename)
     * @param lineNo   (context info) the line number in source for script
     * @param columnNo (context info) the column number in source for script
     * @param script   the script to compile
     * @param cb       code buffer to compile into
     *
     * @exception BSFException if any error while compiling the script
     */
    public void compileScript(String lang,
                              String source,
                              int lineNo,
                              int columnNo,
                              Object script,
                              CodeBuffer cb)
        throws BSFException {
    	logger.debug("BSFManager:compileScript");

        final BSFEngine e = loadScriptingEngine(lang);
        final String sourcef = source;
        final int lineNof = lineNo, columnNof = columnNo;
        final Object scriptf = script;
        final CodeBuffer cbf = cb;

        try {
            AccessController.doPrivileged(new PrivilegedExceptionAction() {
                    public Object run() throws Exception {
                        e.compileScript(sourcef, lineNof, columnNof,
                                        scriptf, cbf);
                        return null;
                    }
                });
        } catch (PrivilegedActionException prive) {

        	logger.error("[BSFManager] Exception :", prive);
            throw (BSFException) prive.getException();
        }
    }

    /**
     * Declare a bean. The difference between declaring and registering
     * is that engines are spsed to make declared beans "pre-available"
     * in the scripts as far as possible. That is, if a script author
     * needs a registered bean, he needs to look it up in some way. However
     * if he needs a declared bean, the language has the responsibility to
     * make those beans avaialable "automatically."
     * <p>
     * When a bean is declared it is automatically registered as well
     * so that any declared bean can be gotton to by looking it up as well.
     * <p>
     * If any of the languages that are already running in this manager
     * says they don't like this (by throwing an exception) then this
     * method will simply quit with that exception. That is, any engines
     * that come after than in the engine enumeration will not even be
     * told about this new bean.
     * <p>
     * So, in general its best to declare beans before the manager has
     * been asked to load any engines because then the user can be informed
     * when an engine rejects it. Also, its much more likely that an engine
     * can declare a bean at start time than it can at any time.
     *
     * @param beanName name to declare bean as
     * @param bean     the bean that's being declared
     * @param type     the type to represent the bean as
     *
     * @exception BSFException if any of the languages that are already
     *            running decides to throw an exception when asked to
     *            declare this bean.
     */
    public void declareBean(String beanName, Object bean, Class type)
        throws BSFException {
    	logger.debug("BSFManager:declareBean");

        registerBean(beanName, bean);

        BSFDeclaredBean tempBean = new BSFDeclaredBean(beanName, bean, type);
        declaredBeans.addElement(tempBean);

        Enumeration enginesEnum = loadedEngines.elements();
        BSFEngine engine;
        while (enginesEnum.hasMoreElements()) {
            engine = (BSFEngine) enginesEnum.nextElement();
            engine.declareBean(tempBean);
        }
    }

    /**
     * Evaluate the given expression of the given language and return the
     * resulting value.
     *
     * @param lang language identifier
     * @param source (context info) the source of this expression
     (e.g., filename)
     * @param lineNo (context info) the line number in source for expr
     * @param columnNo (context info) the column number in source for expr
     * @param expr the expression to evaluate
     *
     * @exception BSFException if anything goes wrong while running the script
     */
    public Object eval(String lang,
                       String source,
                       int lineNo,
                       int columnNo,
                       Object expr)
        throws BSFException {
    	logger.debug("BSFManager:eval");

        final BSFEngine e = loadScriptingEngine(lang);
        final String sourcef = source;
        final int lineNof = lineNo, columnNof = columnNo;
        final Object exprf = expr;
        Object result = null;

        try {
            final Object resultf =
                AccessController.doPrivileged(new PrivilegedExceptionAction() {
                        public Object run() throws Exception {
                            return e.eval(sourcef, lineNof, columnNof, exprf);
                        }
                    });
            result = resultf;
        } catch (PrivilegedActionException prive) {

        	logger.error("[BSFManager] Exception: ", prive);
            throw (BSFException) prive.getException();
        }

        return result;
    }

    //////////////////////////////////////////////////////////////////////
    //
    // Convenience functions for exec'ing and eval'ing scripts directly
    // without loading and dealing with engines etc..
    //
    //////////////////////////////////////////////////////////////////////

    /**
     * Execute the given script of the given language.
     *
     * @param lang     language identifier
     * @param source   (context info) the source of this expression
     (e.g., filename)
     * @param lineNo   (context info) the line number in source for expr
     * @param columnNo (context info) the column number in source for expr
     * @param script   the script to execute
     *
     * @exception BSFException if anything goes wrong while running the script
     */
    public void exec(String lang,
                     String source,
                     int lineNo,
                     int columnNo,
                     Object script)
        throws BSFException {
    	logger.debug("BSFManager:exec");

        final BSFEngine e = loadScriptingEngine(lang);
        final String sourcef = source;
        final int lineNof = lineNo, columnNof = columnNo;
        final Object scriptf = script;

        try {
            AccessController.doPrivileged(new PrivilegedExceptionAction() {
                    public Object run() throws Exception {
                        e.exec(sourcef, lineNof, columnNof, scriptf);
                        return null;
                    }
                });
        } catch (PrivilegedActionException prive) {

        	logger.error("[BSFManager] Exception :", prive);
            throw (BSFException) prive.getException();
        }
    }

    /**
     * Execute the given script of the given language, attempting to
     * emulate an interactive session w/ the language.
     *
     * @param lang     language identifier
     * @param source   (context info) the source of this expression
     *                 (e.g., filename)
     * @param lineNo   (context info) the line number in source for expr
     * @param columnNo (context info) the column number in source for expr
     * @param script   the script to execute
     *
     * @exception BSFException if anything goes wrong while running the script
     */
    public void iexec(String lang,
                     String source,
                     int lineNo,
                     int columnNo,
                     Object script)
        throws BSFException {
    	logger.debug("BSFManager:iexec");

        final BSFEngine e = loadScriptingEngine(lang);
        final String sourcef = source;
        final int lineNof = lineNo, columnNof = columnNo;
        final Object scriptf = script;

        try {
            AccessController.doPrivileged(new PrivilegedExceptionAction() {
                    public Object run() throws Exception {
                        e.iexec(sourcef, lineNof, columnNof, scriptf);
                        return null;
                    }
                });
        } catch (PrivilegedActionException prive) {

        	logger.error("[BSFManager] Exception :", prive);
            throw (BSFException) prive.getException();
        }
    }

    /**
     * Get classLoader
     */
    public ClassLoader getClassLoader() {
    	logger.debug("BSFManager:getClassLoader");
        return classLoader;
    }

    /**
     * Get classPath
     */
    public String getClassPath() {
    	logger.debug("BSFManager:getClassPath");
        if (classPath == null) {
            try {
                classPath = System.getProperty("java.class.path");
            } catch (Throwable t) {

            	logger.debug("[BSFManager] Exception :", t);
                // prolly a security exception .. so no can do
            }
        }
        return classPath;
    }

    /**
     * Determine the language of a script file by looking at the file
     * extension.
     *
     * @param fileName the name of the file
     *
     * @return the scripting language the file is in if the file extension
     *         is known to me (must have been registered via
     *         registerScriptingEngine).
     *
     * @exception BSFException if file's extension is unknown.
     */
    public static String getLangFromFilename(String fileName)
        throws BSFException {
        int dotIndex = fileName.lastIndexOf(".");

        if (dotIndex != -1) {
            String extn = fileName.substring(dotIndex + 1);
            String langval = (String) extn2Lang.get(extn);
            String lang = null;
            int index, loops = 0;

            if (langval != null) {
                ClassLoader tccl=Thread.currentThread().getContextClassLoader();    // rgf, 2009-09-10

                while ((index = langval.indexOf(":", 0)) != -1) {
                    // Great. Multiple language engines registered
                    // for this extension.
                    // Try to find first one that is in our classpath.
                    lang = langval.substring(0, index);
                    langval = langval.substring(index + 1);
                    loops++;

                    // Test to see if in classpath
                    String engineName=null;
                    try {
                        engineName =
                            (String) registeredEngines.get(lang);
                        // Class.forName(engineName);

/*
                        Thread.currentThread().getContextClassLoader().loadClass (engineName); // rgf, 2007-01-28
                    } catch (ClassNotFoundException cnfe) { // 20070917: hmm, maybe defCL can load it ?
                        try {
                            definedClassLoader.loadClass(engineName);
                        }
                        catch (ClassNotFoundException cnfe2) {
                            // Bummer.
                            lang = langval;
                            continue;
                        }
*/

                        // rgf, 2009-09-10
                        boolean bTryDefinedClassLoader=false;
                        if (tccl!=null)     // context CL available, try it first
                        {
                            try
                            {
                                tccl.loadClass (engineName);
                            }
                            catch (ClassNotFoundException cnfe)
                            {
                                bTryDefinedClassLoader=true;
                            }
                        }

                        if (bTryDefinedClassLoader || tccl==null)   // not found, try defined CL next
                        {
                            definedClassLoader.loadClass(engineName);
                        }
                    }
                    catch (ClassNotFoundException cnfe2) {
                        // Bummer.
                        lang = langval;
                        continue;
                    }

                    // Got past that? Good.
                    break;
                }
                if (loops == 0) { lang = langval; }
            }

            if (lang != null && lang != "") {
                return lang;
            }
        }
        throw new BSFException(BSFException.REASON_OTHER_ERROR,
                               "[BSFManager.getLangFromFilename] file extension missing or unknown: "
                               + "unable to determine language for '"
                               + fileName
                               + "'");
    }

    /**
     * Return the current object registry of the manager.
     *
     * @return the current registry.
     */
    public ObjectRegistry getObjectRegistry() {
        return objectRegistry;
    }

    /**
     * Get tempDir
     */
    public String getTempDir() {
        return tempDir;
    }

    /**
     * Determine whether a language is registered.
     *
     * @param lang string identifying a language
     *
     * @return true iff it is
     */
    public static boolean isLanguageRegistered(String lang) {
        return (registeredEngines.get(lang) != null);
    }

    //////////////////////////////////////////////////////////////////////
    //
    // Bean scripting framework services
    //
    //////////////////////////////////////////////////////////////////////

    /**
     * Load a scripting engine based on the lang string identifying it.
     *
     * @param lang string identifying language
     * @exception BSFException if the language is unknown (i.e., if it
     *            has not been registered) with a reason of
     *            REASON_UNKNOWN_LANGUAGE. If the language is known but
     *            if the interface can't be created for some reason, then
     *            the reason is set to REASON_OTHER_ERROR and the actual
     *            exception is passed on as well.
     */
    public BSFEngine loadScriptingEngine(String lang) throws BSFException {
    	logger.debug("BSFManager:loadScriptingEngine");

        // if its already loaded return that
        BSFEngine eng = (BSFEngine) loadedEngines.get(lang);
        if (eng != null) {
            return eng;
        }

        // is it a registered language?
        String engineClassName = (String) registeredEngines.get(lang);
        if (engineClassName == null) {
        	logger.error("[BSFManager] unsupported language: " + lang);
            throw new BSFException(BSFException.REASON_UNKNOWN_LANGUAGE,
                                   "[BSFManager.loadScriptingEngine()] unsupported language: " + lang);
        }

        // create the engine and initialize it. if anything goes wrong
        // except.
        try {

            Class engineClass=null;

            // rgf, 2009-09-10: context CL may be null
            ClassLoader tccl=Thread.currentThread().getContextClassLoader();
            try {  // 20070917
                engineClass = tccl.loadClass (engineClassName);
            }
            catch (ClassNotFoundException cnfe)
            {}

            if (engineClass==null)      // not found, try the defined classLoader
            {
                engineClass = definedClassLoader.loadClass (engineClassName);
            }

               // (classLoader == null)
               //  ? // Class.forName(engineClassName)
               //    Thread.currentThread().getContextClassLoader().loadClass (engineClassName) // rgf, 2007-01-28
               //  : classLoader.loadClass(engineClassName);

            final BSFEngine engf = (BSFEngine) engineClass.newInstance();
            final BSFManager thisf = this;
            final String langf = lang;
            final Vector dbf = declaredBeans;
            AccessController.doPrivileged(new PrivilegedExceptionAction() {
                    public Object run() throws Exception {
                        engf.initialize(thisf, langf, dbf);
                        return null;
                    }
                });
            eng = engf;
            loadedEngines.put(lang, eng);
            pcs.addPropertyChangeListener(eng);
            return eng;
        } catch (PrivilegedActionException prive) {

        	    logger.error("[BSFManager] Exception :", prive);
                throw (BSFException) prive.getException();
        } catch (Throwable t) {

        	logger.error("[BSFManager] Exception :", t);
            throw new BSFException(BSFException.REASON_OTHER_ERROR,
                                   "[BSFManager.loadScriptingEngine()] unable to load language: " + lang,
                                   t);
        }
    }

    /**
     * return a handle to a bean registered in the bean registry by the
     * application or a scripting engine. Returns null if bean is not found.
     *
     * @param beanName name of bean to look up
     *
     * @return the bean if its found or null
     */
    public Object lookupBean(String beanName) {
    	logger.debug("BSFManager:lookupBean");

// String tName="["+Thread.currentThread().getName()+"]";
// System.err.print(">>> thread=["+tName+"]: ["+this+"] BSFManager.lookupBean(\""+beanName+"\") -> " );
// System.err.flush();

        try {
// System.err.println("returning for thread"+tName+" ["+((BSFDeclaredBean)objectRegistry.lookup(beanName)).bean+"]" );
// System.err.flush();
            return ((BSFDeclaredBean)objectRegistry.lookup(beanName)).bean;
        } catch (IllegalArgumentException e) {

// System.err.println("EXCEPTION for thread"+tName+" ["+e.toString()+"], getCause()=["+e.getCause()+"] :-(");
// System.err.flush();
        	logger.debug("[BSFManager] Exception :", e);
            return null;
        }
    }

    /**
     * Registering a bean allows a scripting engine or the application to
     * access that bean by name and to manipulate it.
     *
     * @param beanName name to register under
     * @param bean     the bean to register
     */
    public void registerBean(String beanName, Object bean) {
    	logger.debug("BSFManager:registerBean");

// System.err.println(">>> thread=["+Thread.currentThread().getName()+"]: ["+this+"] BSFManager.registerBean(\""+beanName+"\", ["+bean+"] )" );
// System.err.flush();

        BSFDeclaredBean tempBean;

        if(bean == null) {
            tempBean = new BSFDeclaredBean(beanName, null, null);
        } else {

            tempBean = new BSFDeclaredBean(beanName, bean, bean.getClass());
        }
        objectRegistry.register(beanName, tempBean);
    }

    /**
     * Register a scripting engine in the static registry of the
     * BSFManager.
     *
     * @param lang string identifying language
     * @param engineClassName fully qualified name of the class interfacing
     *        the language to BSF.
     * @param extensions array of file extensions that should be mapped to
     *        this language type. may be null.
     */
    public static void registerScriptingEngine(String lang,
                                               String engineClassName,
                                               String[] extensions) {
        registeredEngines.put(lang, engineClassName);
        if (extensions != null) {
            for (int i = 0; i < extensions.length; i++) {
                String langstr = (String) extn2Lang.get(extensions[i]);
                langstr = (langstr == null) ? lang : lang + ":" + langstr;
                extn2Lang.put(extensions[i], langstr);
            }
        }
    }


    /**
     * Set the class loader for those that need to use it. Default is he
     * who loaded me or null (i.e., its Class.forName).
     *
     * @param classLoader the class loader to use.
     */
    public void setClassLoader(ClassLoader classLoader) {
    	logger.debug("BSFManager:setClassLoader");

        pcs.firePropertyChange("classLoader", this.classLoader, classLoader);
        this.classLoader = classLoader;
    }

    /**
     * Set the classpath for those that need to use it. Default is the value
     * of the java.class.path property.
     *
     * @param classPath the classpath to use
     */
    public void setClassPath(String classPath) {
    	logger.debug("BSFManager:setClassPath");

        pcs.firePropertyChange("classPath", this.classPath, classPath);
        this.classPath = classPath;
    }

    /**
     * Set the object registry used by this manager. By default a new
     * one is created when the manager is new'ed and this overwrites
     * that one.
     *
     * @param objectRegistry the registry to use
     */
    public void setObjectRegistry(ObjectRegistry objectRegistry) {
    	logger.debug("BSFManager:setObjectRegistry");

        this.objectRegistry = objectRegistry;
    }

    /**
     * Temporary directory to put stuff into (for those who need to). Note
     * that unless this directory is in the classpath or unless the
     * classloader knows to look in here, any classes here will not
     * be found! BSFManager provides a service method to load a class
     * which uses either the classLoader provided by the class loader
     * property or, if that fails, a class loader which knows to load from
     * the tempdir to try to load the class. Default value of tempDir
     * is "." (current working dir).
     *
     * @param tempDir the temporary directory
     */
    public void setTempDir(String tempDir) {
    	logger.debug("BSFManager:setTempDir");

        pcs.firePropertyChange("tempDir", this.tempDir, tempDir);
        this.tempDir = tempDir;
    }

    /**
     * Gracefully terminate all engines
     */
    public void terminate() {
    	logger.debug("BSFManager:terminate");

        Enumeration enginesEnum = loadedEngines.elements();
        BSFEngine engine;
        while (enginesEnum.hasMoreElements()) {
            engine = (BSFEngine) enginesEnum.nextElement();

            pcs.removePropertyChangeListener(engine);   // rgf, 2013-06-29: removing memory leak

            engine.terminate();
        }

        loadedEngines = new Hashtable();
    }

    /**
     * Undeclare a previously declared bean. This removes the bean from
     * the list of declared beans in the manager as well as asks every
     * running engine to undeclared the bean. As with above, if any
     * of the engines except when asked to undeclare, this method does
     * not catch that exception. Quietly returns if the bean is unknown.
     *
     * @param beanName name of bean to undeclare
     *
     * @exception BSFException if any of the languages that are already
     *            running decides to throw an exception when asked to
     *            undeclare this bean.
     */
    public void undeclareBean(String beanName) throws BSFException {
    	logger.debug("BSFManager:undeclareBean");



        unregisterBean(beanName);

        BSFDeclaredBean tempBean = null;
        boolean found = false;
        for (Iterator i = declaredBeans.iterator(); i.hasNext();) {
            tempBean = (BSFDeclaredBean) i.next();
            if (tempBean.name.equals(beanName)) {
            	found = true;
                break;
            }
        }

        if (found) {
            declaredBeans.removeElement(tempBean);

            Enumeration enginesEnum = loadedEngines.elements();
            while (enginesEnum.hasMoreElements()) {
                BSFEngine engine = (BSFEngine) enginesEnum.nextElement();
                engine.undeclareBean(tempBean);
            }
        }
    }

    /**
     * Unregister a previously registered bean. Silent if name is not found.
     *
     * @param beanName name of bean to unregister
     */
    public void unregisterBean(String beanName) {
    	logger.debug("BSFManager:unregisterBean");

// System.err.println("<<< thread=["+Thread.currentThread().getName()+"]: ["+this+"] BSFManager.unregisterBean(\""+beanName+"\")" );
// System.err.flush();
        objectRegistry.unregister(beanName);
    }


}
