/*
 * Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved. 
 * Use is subject to license terms.
 *
 * Redistribution and use in source and binary forms, with or without modification, are 
 * permitted provided that the following conditions are met: Redistributions of source code 
 * must retain the above copyright notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list of 
 * conditions and the following disclaimer in the documentation and/or other materials 
 * provided with the distribution. Neither the name of the Sun Microsystems nor the names of 
 * is contributors may be used to endorse or promote products derived from this software 
 * without specific prior written permission. 

 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package org.maachang.jsr.script.javascript;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.script.AbstractScriptEngine;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptException;

import org.maachang.jsr.script.api.ApiManager;
import org.maachang.jsr.script.api.ExternalBindings;
import org.maachang.jsr.script.util.ExtendedScriptException;
import org.maachang.jsr.script.util.InterfaceImplementor;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.LazilyLoadedCtor;
import org.mozilla.javascript.NativeJavaClass;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Undefined;
import org.mozilla.javascript.Wrapper;



/**
 * Implementation of <code>ScriptEngine</code> using the Mozilla Rhino
 * interpreter.
 *
 * @author Mike Grogan
 * @author A. Sundararajan
 * @version 1.0
 * @since 1.6
 *
 * Modified for phobos to remove some of the restrictions.
 * Modified to allow subclassing and preprocessing of script source code.
 * Modified to avoid using the RhinoTopLevel class, since that introduces
 * a circularity that prevents objects from being garbage collected.
 *
 * @author Roberto Chinnici
 *
 */
public class RhinoScriptEngine extends AbstractScriptEngine
        implements Invocable, Compilable {
    
    private static final int COMP_OPT_LEVEL = 9 ;
    private static final int EVAL_OPT_LEVEL = 1 ;
    
    public static final boolean DEBUG = false;
    private static final String TOPLEVEL_SCRIPT_NAME = "META-INF/toplevel.js";

    /* Scope where standard JavaScript objects and our
     * extensions to it are stored. Note that these are not
     * user defined engine level global variables. These are
     * variables have to be there on all compliant ECMAScript
     * scopes. We put these standard objects in this top level.
     */
    private ScriptableObject topLevel = null ;

    /* map used to store indexed properties in engine scope
     * refer to comment on 'indexedProps' in ExternalScriptable.java.
     */
    private Map indexedProps;

    private ScriptEngineFactory factory;
    private InterfaceImplementor implementor;
    
    private static final String FUNC_NAMES[] = {
        "globalBindings",
        "print",
        "println",
        //"rhinoContext",
        "isNull",
        "objectClassName",
        "className",
        "isEmpty",
        "useString",
        "isNumeric",
        //"typeof",
        "valueof",
        "convertString",
        "parseInt",
        "parseNumber",
        "_parseValue",
        "trim",
        "startsWith",
        "endsWith",
        "changeString",
        "currentTimeMillis",
    };

    // in Phobos we want to support all javascript features
    /*
    static {
        ContextFactory.initGlobal(new ContextFactory() {
            protected Context makeContext() {
                Context cx = super.makeContext();
                cx.setClassShutter(RhinoClassShutter.getInstance());
                cx.setWrapFactory(RhinoWrapFactory.getInstance());
                return cx;
            }

            public boolean hasFeature(Context cx, int feature) {
                // we do not support E4X (ECMAScript for XML)!
                if (feature == Context.FEATURE_E4X) {
                    return false;
                } else {
                    return super.hasFeature(cx, feature);
                }
            }
        });
    }

    static {
        if (USE_INTERPRETER) {
            ContextFactory.initGlobal(new ContextFactory() {
                protected Context makeContext() {
                    Context cx = super.makeContext();
                    cx.setOptimizationLevel(-1);
                    return cx;
                }
            });
        }
    }*/
    
    
    /**
     * Creates a new instance of RhinoScriptEngine
     */
    public RhinoScriptEngine() {
       
        Context cx = enterContext();
        try { 
            /*
             * RRC - modified this code to register JSAdapter and some functions
             * directly, without using a separate RhinoTopLevel class
             */
            topLevel = new ImporterTopLevel(cx, false);
            new LazilyLoadedCtor(topLevel, "JSAdapter",
                "org.maachang.jsr.script.javascript.JSAdapter",
                false);
            topLevel.defineFunctionProperties(FUNC_NAMES, RhinoScriptEngine.class, ScriptableObject.DONTENUM);
            
            processAllTopLevelScripts(cx);
        } finally {
            //cx.exit();
            Context.exit() ;
        }
        
        indexedProps = new HashMap();
        
        //construct object used to implement getInterface
        implementor = new InterfaceImplementor(this) {
                protected Object convertResult(Method method, Object res)
                                            throws ScriptException {
                    Class desiredType = method.getReturnType();
                    if (desiredType == Void.TYPE) {
                        return null;
                    } else {
                        return Context.jsToJava(res, desiredType);
                    }
                }
        };
        
        //super.context = new NormalScriptContext() ;
    }
    
    public Object eval(Reader reader, ScriptContext ctxt)
    throws ScriptException {
        Object ret;
        
        Context cx = enterContext();
        try {
            cx.setOptimizationLevel( EVAL_OPT_LEVEL ) ;
            Scriptable scope = getRuntimeScope(ctxt);
            scope.put("context", scope, ctxt);

            // NOTE (RRC) - why does it look straight into the engine instead of asking
            // the given ScriptContext object?
            // Modified to use the context
            // String filename = (String) get(ScriptEngine.FILENAME);
            String filename =  ApiManager.getScriptName() ;
            //if (ctxt != null && ctxt.getBindings(ScriptContext.ENGINE_SCOPE) != null) {
            //    filename = (String) ctxt.getBindings(ScriptContext.ENGINE_SCOPE).get(ScriptEngine.FILENAME);
            //}
            //if (filename == null) {
            //    filename = (String) get(ScriptEngine.FILENAME);
            //}
            
            filename = filename == null ? "<Unknown source>" : filename;
            ret = cx.evaluateReader(scope, preProcessScriptSource(reader), filename , 1,  null);
        } catch (JavaScriptException jse) {
            if (DEBUG) jse.printStackTrace();
            int line = (line = jse.lineNumber()) == 0 ? -1 : line;
            Object value = jse.getValue();
            String str = (value != null && value.getClass().getName().equals("org.mozilla.javascript.NativeError") ?
                          value.toString() :
                          jse.toString());
            throw new ExtendedScriptException(jse, str, jse.sourceName(), line);
        } catch (RhinoException re) {
            if (DEBUG) re.printStackTrace();
            int line = (line = re.lineNumber()) == 0 ? -1 : line;
            throw new ExtendedScriptException(re, re.toString(), re.sourceName(), line);
        } catch (IOException ee) {
            throw new ScriptException(ee);
        } finally {
            //cx.exit();
            Context.exit() ;
        }
        
        return unwrapReturnValue(ret);
    }
    
    public Object eval(String script, ScriptContext ctxt) throws ScriptException {
        if (script == null) {
            throw new NullPointerException("null script");
        }
        return eval(preProcessScriptSource(new StringReader(script)) , ctxt);
    }
    
    public ScriptEngineFactory getFactory() {
        if (factory != null) {
            return factory;
        } else {
            return new RhinoScriptEngineFactory();
        }
    }
    
    //Invocable methods
    public Object invokeFunction(String name, Object... args)
    throws ScriptException, NoSuchMethodException {
        return invokeMethod(null, name, args);
    }
    
    public Object invokeMethod(Object thiz, String name, Object... args)
    throws ScriptException, NoSuchMethodException {
        
        Context cx = enterContext();
        try {
            cx.setOptimizationLevel( EVAL_OPT_LEVEL ) ;
            if (name == null) {
                throw new NullPointerException("method name is null");
            }

            if (thiz != null && !(thiz instanceof Scriptable)) {
                thiz = cx.toObject(thiz, topLevel);
            }
            
            Scriptable engineScope = getRuntimeScope(context);
            Scriptable localScope = (thiz != null)? (Scriptable) thiz :
                                                    engineScope;
            Object obj = ScriptableObject.getProperty(localScope, name);
            if (! (obj instanceof Function)) {
                throw new NoSuchMethodException("no such method: " + name);
            }

            Function func = (Function) obj;
            Scriptable scope = func.getParentScope();
            if (scope == null) {
                scope = engineScope;
            }
            Object result = func.call(cx, scope, localScope, 
                                      wrapArguments(args));
            return unwrapReturnValue(result);
        } catch (JavaScriptException jse) {
            if (DEBUG) jse.printStackTrace();
            int line = (line = jse.lineNumber()) == 0 ? -1 : line;
            Object value = jse.getValue();
            String str = (value != null && value.getClass().getName().equals("org.mozilla.javascript.NativeError") ?
                          value.toString() :
                          jse.toString());
            throw new ExtendedScriptException(jse, str, jse.sourceName(), line);
        } catch (RhinoException re) {
            if (DEBUG) re.printStackTrace();
            int line = (line = re.lineNumber()) == 0 ? -1 : line;
            throw new ExtendedScriptException(re, re.toString(), re.sourceName(), line);
        } finally {
            //cx.exit();
            Context.exit() ;
        }
    }
   
    public <T> T getInterface(Class<T> clasz) {
        try {
            return implementor.getInterface(null, clasz);
        } catch (ScriptException e) {
            return null;
        }
    }
    
    public <T> T getInterface(Object thiz, Class<T> clasz) {
        if (thiz == null) {
            throw new IllegalArgumentException("script object can not be null");
        }

        try {
            return implementor.getInterface(thiz, clasz);
        } catch (ScriptException e) {
            return null;
        }
    }

    /*private static final String printSource = 
            "function print(str) {                         \n" +
            "    if (typeof(str) == 'undefined') {         \n" +
            "        str = 'undefined';                    \n" +
            "    } else if (str == null) {                 \n" +
            "        str = 'null';                         \n" +
            "    }                                         \n" +
            "    context.getWriter().println(String(str)); \n" +
            "}";
    */
    
    Scriptable getRuntimeScope(ScriptContext ctxt) {
        if (ctxt == null) {
            throw new NullPointerException("null script context");
        }

        // we create a scope for the given ScriptContext
        Scriptable newScope = new ExternalScriptable(ctxt, indexedProps);

        // Set the prototype of newScope to be 'topLevel' so that
        // JavaScript standard objects are visible from the scope.
        newScope.setPrototype(topLevel);

        // define "context" variable in the new scope
        newScope.put("context", newScope, ctxt);
       
        // define "print" function in the new scope
        //Context cx = enterContext();
        //try {
        //    cx.evaluateString(newScope, printSource, "print", 1, null);
        //} finally {
        //    //cx.exit();
        //    Context.exit() ;
        //}
        return newScope;
    }
    
    
    //Compilable methods
    public CompiledScript compile(String script) throws ScriptException {
        return compile(preProcessScriptSource(new StringReader(script)));
    }
    
    public CompiledScript compile(java.io.Reader script) throws ScriptException {
        CompiledScript ret = null;
        Context cx = enterContext();
        
        try {
            cx.setOptimizationLevel( COMP_OPT_LEVEL ) ;
            //String filename = (String) get(ScriptEngine.FILENAME);
            String filename =  ApiManager.getScriptName() ;
            if (filename == null) {
                filename = "<Unknown Source>";
            }
            
            Scriptable scope = getRuntimeScope(context);
            Script scr = cx.compileReader(scope, preProcessScriptSource(script), filename, 1, null);
            ret = new RhinoCompiledScript(this, scr);
        } catch (Exception e) {
            if (DEBUG) e.printStackTrace();
            throw new ScriptException(e);
        } finally {
            //cx.exit();
            Context.exit() ;
        }
        return ret;
    }
    
    
    //package-private helpers

    static Context enterContext() {
        // call this always so that initializer of this class runs
        // and initializes custom wrap factory and class shutter.
        return Context.enter();
    }

    void setEngineFactory(ScriptEngineFactory fac) {
        factory = fac;
    }

    Object[] wrapArguments(Object[] args) {
        if (args == null) {
            return Context.emptyArgs;
        }
        Object[] res = new Object[args.length];
        for (int i = 0; i < res.length; i++) {
            res[i] = Context.javaToJS(args[i], topLevel);
        }
        return res;
    }
    
    Object unwrapReturnValue(Object result) {
        if (result instanceof Wrapper) {
            result = ( (Wrapper) result).unwrap();
        }
        
        return result instanceof Undefined ? null : result;
    }
    
    public Bindings createBindings() {
        return ExternalBindings.getInstance() ;
    }
    
   /**
    * We convert script values to the nearest Java value.
    * We unwrap wrapped Java objects so that access from
    * Bindings.get() would return "workable" value for Java.
    * But, at the same time, we need to make few special cases
    * and hence the following function is used.
    */
    public static final Object jsToJava(Object jsObj) {
        if (jsObj instanceof Wrapper) {
            Wrapper njb = (Wrapper) jsObj;
            /* importClass feature of ImporterTopLevel puts
             * NativeJavaClass in global scope. If we unwrap
             * it, importClass won't work.
             */
            if (njb instanceof NativeJavaClass) {
                return njb;
            }

            /* script may use Java primitive wrapper type objects
             * (such as java.lang.Integer, java.lang.Boolean etc)
             * explicitly. If we unwrap, then these script objects
             * will become script primitive types. For example,
             * 
             *    var x = new java.lang.Double(3.0); print(typeof x);
             * 
             * will print 'number'. We don't want that to happen.
             */
            Object obj = njb.unwrap();
            if (obj instanceof Number || obj instanceof String ||
                obj instanceof Boolean || obj instanceof Character) {
                // special type wrapped -- we just leave it as is.
                return njb;
            } else {
                // return unwrapped object for any other object.
                return obj;
            }
        } else { // not-a-Java-wrapper
            return jsObj;
        }
    }
    
    protected Reader preProcessScriptSource(Reader reader) throws ScriptException {
        return reader;
    }

    protected void processAllTopLevelScripts(Context cx) {
        processTopLevelScript(TOPLEVEL_SCRIPT_NAME, cx);
    }

    protected void processTopLevelScript(String scriptName, Context cx) {    
        InputStream toplevelScript = this.getClass().getClassLoader().getResourceAsStream(scriptName);
        if (toplevelScript != null) {
            Reader reader = new InputStreamReader(toplevelScript);
            try {
                cx.evaluateReader(topLevel, reader, scriptName, 1, null);
            }
            catch (Exception e) {
                if (DEBUG) e.printStackTrace();
            }
            finally {
                try {
                    toplevelScript.close();
                }
                catch (IOException e) {
                }
            }
        }
    }
    
    public static final Object nativeTo( Object o ) {
        if( o == null || o == Undefined.instance ) {
            return o ;
        }
        if( o instanceof Wrapper ) {
            o = ( ( Wrapper )o ).unwrap() ;
        }
        return o ;
    }
    
    /** 現在のBindingsを取得. */
    public static final Object globalBindings(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length == 1) {
            Object arg = args[0];
            if (arg instanceof Wrapper) {
                arg = ((Wrapper)arg).unwrap();
            }
            if (arg instanceof ExternalScriptable) {
                ScriptContext ctx = ((ExternalScriptable)arg).getContext();
                Bindings bind = ctx.getBindings(ScriptContext.ENGINE_SCOPE);
                return Context.javaToJS(bind, 
                           ScriptableObject.getTopLevelScope(thisObj));
            }
        }
        return Context.getUndefinedValue();
    }
    
    /** 文字列をコンソール出力. */
    public static final Object print(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length < 1) {
            return null ;
        }
        else {
            //System.out.print( Context.toString( nativeTo( args[ 0 ] ) ) ) ;
            System.out.print( _convertString( nativeTo( args[ 0 ] ) ) ) ;
        }
        return null ;
    }
    
    /** 文字列をコンソール出力. */
    public static final Object println(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length < 1) {
            System.out.println() ;
        }
        else {
            //System.out.println( Context.toString( nativeTo( args[ 0 ] ) ) ) ;
            System.out.println( _convertString( nativeTo( args[ 0 ] ) ) ) ;
        }
        return null ;
    }
    
    /** コンテキストを取得 **/
    public static final Object rhinoContext(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        return cx ;
    }
    
    /** nullチェック */
    public static final Object isNull(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length < 1) {
            return Boolean.FALSE ;
        }
        Object o = nativeTo( args[ 0 ] ) ;
        if( o == null || o == Undefined.instance ) {
            return Boolean.TRUE ;
        }
        return Boolean.FALSE ;
    }
    
    /** クラス名を取得. */
    public static final Object objectClassName(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length < 1) {
            return null ;
        }
        Object o = nativeTo( args[ 0 ] ) ;
        if( o == null || o == Undefined.instance ) {
            return null ;
        }
        return o.getClass().getName() ;
    }
    
    /** クラス名のオブジェクト名だけを取得. */
    public static final Object className(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length < 1) {
            return null ;
        }
        Object o = nativeTo( args[ 0 ] ) ;
        if( o == null || o == Undefined.instance ) {
            return null ;
        }
        return className( o ) ;
    }
    
    /** 指定情報が存在しないかチェック. */
    public static final Object isEmpty(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length < 1) {
            return Boolean.TRUE ;
        }
        Object o = nativeTo( args[ 0 ] ) ;
        if( o == null || o == Undefined.instance ) {
            return Boolean.TRUE ;
        }
        String s = className( o ) ;
        if( "JSAdapter".equals( s ) ) {
            JSAdapter js = ( JSAdapter )o ;
            if( js.has( "empty",thisObj ) == false ) {
                return Boolean.TRUE ;
            }
            o = js.get( "empty",thisObj ) ;
            if( o == null ) {
                return Boolean.TRUE ;
            }
            o = nativeTo( o ) ;
            if( ( o instanceof Boolean ) == false ) {
                return Boolean.TRUE ;
            }
            return o ;
        }
        else if( "NativeObject".equals( s ) ) {
            Object[] lst = ( ( Scriptable )o ).getIds() ;
            if( lst != null && lst.length > 0 ) {
                return Boolean.FALSE ;
            }
            return Boolean.TRUE ;
        }
        else if( "NativeArray".equals( s ) ) {
            o = ( ( Scriptable )o ).get( "length",thisObj ) ;
            if( o == null ) {
                return Boolean.TRUE ;
            }
            o = nativeTo( o ) ;
            if( o instanceof Double && ( ( Double )o ).intValue() > 0 ) {
                return Boolean.FALSE ;
            }
            return Boolean.TRUE ;
        }
        else if( "String".equals( s ) ) {
            return ( "".equals( o ) ) ? Boolean.TRUE : Boolean.FALSE ;
        }
        else if( "NativeString".equals( s ) ) {
            return ( "".equals( o.toString() ) ) ? Boolean.TRUE : Boolean.FALSE ;
        }
        else if( o instanceof byte[] ) {
            return ( ( ( byte[] )o ).length <= 0 ) ? Boolean.TRUE : Boolean.FALSE ;
        }
        else if( o instanceof Map ) {
            return ( ( ( Map )o ).size() <= 0 ) ? Boolean.TRUE : Boolean.FALSE ;
        }
        else if( o instanceof List ) {
            return ( ( ( List )o ).size() <= 0 ) ? Boolean.TRUE : Boolean.FALSE ;
        }
        return Boolean.FALSE ;
    }
    
    /** 文字列が存在するかチェック. */
    public static final Object useString(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length < 1) {
            return Boolean.FALSE ;
        }
        Object o = nativeTo( args[ 0 ] ) ;
        if( o == null || o == Undefined.instance ) {
            return Boolean.FALSE ;
        }
        String s = className( o ) ;
        if( "NativeBoolean".equals( s ) || "Boolean".equals( s ) ||
            "Double".equals( s ) || "Float".equals( s ) ||
            "Integer".equals( s ) || "Long".equals( s ) || "Short".equals( s ) ||
            ( "NativeString".equals( s ) && isUseString( o.toString() ) ) ||
            ( "String".equals( s ) && isUseString( (String)o ) ) ) {
            return Boolean.TRUE ;
        }
        return Boolean.FALSE ;
    }
    
    /** 指定内容が数字であるかチェック */
    public static final Object isNumeric(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length < 1) {
            return Boolean.FALSE ;
        }
        Object o = nativeTo( args[ 0 ] ) ;
        if( o == null || o == Undefined.instance ) {
            return Boolean.FALSE ;
        }
        return ( isNumber( null,o ) ) ? Boolean.TRUE : Boolean.FALSE ;
    }
    
    /** オブジェクトタイプ判別 */
    public static final Object typeof(Context cx, Scriptable thisObj, Object[] args,
        Function funObj) {
        if (args.length < 1) {
            return "undefined" ;
        }
        return objectType( nativeTo( args[ 0 ] ),thisObj ) ;
    }
    
    /** オブジェクトタイプ判別 */
    public static final Object valueof(Context cx, Scriptable thisObj, Object[] args,
        Function funObj) {
        if (args.length < 1) {
            return "undefined" ;
        }
        return objectType( nativeTo( args[ 0 ] ),thisObj ) ;
    }
    
    /** 文字列変換処理 */
    public static final Object convertString(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length < 1) {
            return "" ;
        }
        Object o = _convertString( nativeTo( args[ 0 ] ) ) ;
        if( o == null ) {
            return "" ;
            //return Context.toString( "" ) ;
        }
        return o ;
        //return o.toString() ;
        //return Context.toString( o ) ;
    }
    
    /** 整数変換処理 */
    public static final Object parseInt(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length < 1) {
            return Undefined.instance ;
        }
        Object o = nativeTo( args[ 0 ] ) ;
        if( o == null ) {
            return null ;
        }
        else if( o == Undefined.instance ) {
            return Undefined.instance ;
        }
        return _convertInt( o ) ;
    }
    
    /** 数値変換処理 */
    public static final Object parseNumber(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length < 1) {
            return Undefined.instance ;
        }
        Object o = nativeTo( args[ 0 ] ) ;
        if( o == null ) {
            return null ;
        }
        else if( o == Undefined.instance ) {
            return Undefined.instance ;
        }
        int[] dot = new int[ 1 ] ;
        // 数値の場合は、数値で返す.
        if( isNumber( dot,o ) ) {
            if( dot[ 0 ] == 0x00000000 ) {
                if( ( o instanceof Long ) == false ) {
                    o = new Long( o.toString() ) ;
                }
                return o ;
            }
            else if( dot[ 0 ] == 0x00000001 ) {
                return _convertNumber( ( Double )o ) ;
            }
            else if( dot[ 0 ] == 0x00000002 ) {
                return _convertNumber( ( Float )o ) ;
            }
            else if( dot[ 0 ] == 0x00000003 ) {
                return new Long( o.toString() ) ;
            }
            else if( dot[ 0 ] == 0x00000004 ) {
                return _convertNumber( new Double( o.toString() ) ) ;
            }
        }
        return "NaN" ;
    }
    
    /** オブジェクトを最適な条件に変換 */
    public static final Object _parseValue(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length < 1) {
            return "" ;
        }
        Object o = nativeTo( args[ 0 ] ) ;
        if( o == null || o == Undefined.instance ) {
            return "" ;
        }
        int[] dot = new int[ 1 ] ;
        // 数値の場合は、数値で返す.
        if( isNumber( dot,o ) ) {
            if( dot[ 0 ] == 0x00000000 ) {
                if( ( o instanceof Long ) == false ) {
                    o = new Long( o.toString() ) ;
                }
                return o ;
            }
            else if( dot[ 0 ] == 0x00000001 ) {
                return _convertNumber( ( Double )o ) ;
            }
            else if( dot[ 0 ] == 0x00000002 ) {
                return _convertNumber( ( Float )o ) ;
            }
            else if( dot[ 0 ] == 0x00000003 ) {
                String s = o.toString() ;
                if( s.length() > 1 && s.charAt( 0 ) == '0' ) {
                    return Context.toString( s ) ;
                }
                return new Long( o.toString() ) ;
            }
            else if( dot[ 0 ] == 0x00000004 ) {
                return _convertNumber( new Double( o.toString() ) ) ;
            }
        }
        dot = null ;
        String value = null ;
        // 文字列の場合.
        String s = className( o ) ;
        if( "String".equals( s ) ) {
            value = ( String )o ;
        }
        else if( "NativeString".equals( s ) ) {
            value = o.toString() ;
        }
        // 文字列以外の場合は、そのまま返す.
        else {
            return args[ 0 ] ;
        }
        s = null ;
        // 文字列内が、[true],[false]の場合はBooleanで返す.
        String x = value.toLowerCase() ;
        if( "true".equals( x ) ) {
            return Boolean.TRUE ;
        }
        else if( "false".equals( x ) ) {
            return Boolean.FALSE ;
        }
        // それ以外の場合は、Context変換した内容で返す.
        return Context.toString( value ) ;
    }
    
    /** 文字の前後スペースを省く. */
    public static final Object trim(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length < 1) {
            return "" ;
        }
        String str = _convertString( nativeTo( args[ 0 ] ) ) ;
        if( str != null ) {
            return Context.toString( trimString( str ) ) ;
        }
        return args[ 0 ] ;
    }
    
    /** 先頭の文字チェック. */
    public static final Object startsWith(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length < 2) {
            return Boolean.FALSE ;
        }
        String str = _convertString( nativeTo( args[ 0 ] ) ) ;
        String chk = _convertString( nativeTo( args[ 1 ] ) ) ;
        if( str != null && str.length() > 0 && chk != null && chk.length() > 0 ) {
            return ( str.startsWith( chk ) ) ? Boolean.TRUE : Boolean.FALSE ;
        }
        return Boolean.FALSE ;
    }
    
    /** 最後の文字チェック. */
    public static final Object endsWith(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length < 2) {
            return Boolean.FALSE ;
        }
        String str = _convertString( nativeTo( args[ 0 ] ) ) ;
        String chk = _convertString( nativeTo( args[ 1 ] ) ) ;
        if( str != null && str.length() > 0 && chk != null && chk.length() > 0 ) {
            return ( str.endsWith( chk ) ) ? Boolean.TRUE : Boolean.FALSE ;
        }
        return Boolean.FALSE ;
    }
    
    /** 指定文字変換. */
    public static final Object changeString(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        if (args.length < 3) {
            return null ;
        }
        String str = _convertString( nativeTo( args[ 0 ] ) ) ;
        String src = _convertString( nativeTo( args[ 1 ] ) ) ;
        String dest = _convertString( nativeTo( args[ 2 ] ) ) ;
        if( str != null && str.length() > 0 && src != null && src.length() > 0 &&
            dest != null && dest.length() > 0 ) {
            return Context.toString( _changeString( str,src,dest ) ) ;
        }
        return args[ 0 ] ;
    }
    
    /** 現在時間を取得. */
    public static final Object currentTimeMillis(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) {
        return new Long( System.currentTimeMillis() ) ;
    }
    
    
    // private系.
    
    private static final String className( Object o ) {
        if( o == null ) {
            return "Null" ;
        }
        String s = o.getClass().getName() ;
        int p = s.lastIndexOf( "." ) ;
        if( p != -1 ) {
            return s.substring( p+1 ) ;
        }
        return s ;
    }
    
    private static final boolean isNumber( int[] dot,Object o ) {
        if( dot != null && dot.length > 0 ) {
            dot[ 0 ] = 0x00000000 ;
        }
        String s = className( o ) ;
        if( "Double".equals( s ) || "Float".equals( s ) ||
            "Integer".equals( s ) || "Long".equals( s ) || "Short".equals( s ) ) {
            if( dot != null && dot.length > 0 ) {
                if( "Double".equals( s ) ) {
                    dot[ 0 ] = 0x00000001 ;
                }
                else if( "Float".equals( s ) ) {
                    dot[ 0 ] = 0x00000002 ;
                }
            }
            return true ;
        }
        if( dot != null && dot.length > 0 ) {
            dot[ 0 ] = 0x00000003 ;
        }
        return isNumberByString( dot,o,s ) ;
    }
    
    private static final boolean isNumberByString( int[] dot,Object o,String s ) {
        if( s == null ) {
            s = className( o ) ;
        }
        String num = null ;
        if( "String".equals( s ) ) {
            num = trimString( ( String )o ) ;
        }
        else if( "NativeString".equals( s ) || "NativeNumber".equals( s ) ) {
            num = trimString( o.toString() ) ;
        }
        else {
            return false ;
        }
        if( num == null || ( num = trimString( num ) ).length() <= 0 ) {
            return false ;
        }
        s = null ;
        int start = 0 ;
        if( num.startsWith( "-" ) ) {
            start = 1 ;
        }
        boolean dt = false ;
        int len = num.length() ;
        if( start < len ) {
            for( int i = start ; i < len ; i ++ ) {
                char c = num.charAt( i ) ;
                if( c == '.' ) {
                    if( dt ) {
                        return false ;
                    }
                    if( dot != null && dot.length > 0 ) {
                        dot[ 0 ] = 0x00000004 ;
                    }
                    dt = true ;
                }
                else if( ( c >= '0' && c <= '9' ) == false ) {
                    return false ;
                }
            }
        }
        else {
            return false ;
        }
        return true ;
    }
    
    private static final String objectType( Object o,Scriptable thisObj ) {
        if( o == null ) {
            return "null" ;
        }
        if( o == Undefined.instance ) {
            return "undefined" ;
        }
        if( o instanceof byte[] ) {
            return "binary" ;
        }
        String s = className( o ) ;
        if( "String".equals( s ) || "NativeString".equals( s ) ) {
            return "string" ;
        }
        if( "Boolean".equals( s ) || "NativeBoolean".equals( s ) ) {
            return "boolean" ;
        }
        if( "Double".equals( s ) || "Float".equals( s ) ||
            "Integer".equals( s ) || "Long".equals( s ) || "Short".equals( s ) ||
            "NativeNumber".equals( s ) ) {
            return "number" ;
        }
        if( "NativeArray".equals( s ) ) {
            return "array" ;
        }
        if( "NativeDate".equals( s ) ) {
            return "date" ;
        }
        if( "JSAdapter".equals( s ) ) {
            JSAdapter js = ( JSAdapter )o ;
            String type = ( String )js.get( "__getJSAdapterType",thisObj ) ;
            if( type != null ) {
                return type ;
            }
            return "JSAdapter" ;
        }
        if( o instanceof Function || "FunctionObject".equals( s ) ) {
            return "function" ;
        }
        if( "NativeObject".equals( s ) ) {
            Object[] lst = ( ( Scriptable )o ).getIds() ;
            int len = ( lst != null ) ? lst.length : 0 ;
            lst = null ;
            if( len > 0 ) {
                return "map" ;
            }
            //return "map" ;
        }
        return "object" ;
    }
    
    /**
     * 文字列に変換.
     * @param o 対象のオブジェクトを設定します.
     * @return String 変換された内容が返されます.
     */
    public static final String _convertString( Object o ) {
        if( o == null || o == Undefined.instance ) {
            return "" ;
        }
        if( o instanceof String ) {
            String s = ( String )o ;
            if( ".".equals( s ) || s.endsWith( "." ) ) {
                return s ;
            }
        }
        //return Context.toString( o ) ;
        String clz = className( o ) ;
        if( "NativeDate".equals( clz ) ) {
            return Context.toString( o ) ;
        }
        if( "Double".equals( clz ) ) {
            Double d = ( java.lang.Double )o ;
            long i = d.longValue() ;
            if( d.doubleValue() == i ) {
                return String.valueOf( i ) ;
            }
            else {
                return o.toString() ;
            }
        }
        else if( "Float".equals( clz ) ) {
            Float d = ( java.lang.Float )o ;
            long i = d.longValue() ;
            if( d.floatValue() == i ) {
                return String.valueOf( i ) ;
            }
            else {
                return o.toString() ;
            }
        }
        else if( isNumberByString( null,o,clz ) ) {
            String s = _toString( o ) ;
            int p = s.indexOf( "." ) ;
            if( p != -1 ) {
                int len = s.length() ;
                for( int i = p+1 ; i < len ; i ++ ) {
                    char c = s.charAt( i ) ;
                    if( c != '0' ) {
                        return s ;
                    }
                }
                return s.substring( 0,p ) ;
            }
            return s ;
        }
        return o.toString() ;
    }
    
    // 数値系の場合は、強制変換.
    private static final Object _convertInt( Object o ) {
        if( o == null ) {
            return "" ;
        }
        else if( o == Undefined.instance ) {
            return "undefined" ;
        }
        if( o instanceof String ) {
            String s = ( String )o ;
            if( ".".equals( s ) || s.endsWith( "." ) ) {
                return s ;
            }
        }
        String clz = className( o ) ;
        if( "Long".equals( clz ) ) {
            return o ;
        }
        else if( "Short".equals( clz ) || "Integer".equals( clz ) ) {
            return new Long( o.toString() ) ;
        }
        else if( "Double".equals( clz ) ) {
            return new Long( ( ( java.lang.Double )o ).longValue() ) ;
        }
        else if( "Float".equals( clz ) ) {
            return new Long( ( ( java.lang.Float )o ).longValue() ) ;
        }
        else if( isNumberByString( null,o,clz ) ) {
            String s = _toString( o ) ;
            int p = s.indexOf( "." ) ;
            if( p != -1 ) {
                s = s.substring( 0,p ) ;
            }
            return new Long( s ) ;
        }
        return "NaN" ;
    }
    
    // 小数点に対して、小数点値が0[たとえば100.0f]の場合は
    // Long値に変換.
    private static final Object _convertNumber( Object o ) {
        if( o == null ) {
            return "" ;
        }
        else if( o == Undefined.instance ) {
            return "undefined" ;
        }
        if( o instanceof String ) {
            String s = ( String )o ;
            if( ".".equals( s ) || s.endsWith( "." ) ) {
                return s ;
            }
        }
        String clz = className( o ) ;
        if( "Double".equals( clz ) ) {
            Double d = ( java.lang.Double )o ;
            long i = d.longValue() ;
            if( d.doubleValue() == i ) {
                return new java.lang.Long( i ) ;
            }
        }
        else if( "Float".equals( clz ) ) {
            Float d = ( java.lang.Float )o ;
            long i = d.longValue() ;
            if( d.floatValue() == i ) {
                return new java.lang.Long( i ) ;
            }
            else {
                o = new Double( d.doubleValue() ) ;
            }
        }
        else if( isNumberByString( null,o,clz ) ) {
            String s = _toString( o ) ;
            int p = s.indexOf( "." ) ;
            if( p != -1 ) {
                int len = s.length() ;
                for( int i = p+1 ; i < len ; i ++ ) {
                    char c = s.charAt( i ) ;
                    if( c != '0' ) {
                        return new java.lang.Double( s ) ;
                    }
                }
                s = s.substring( 0,p ) ;
            }
            return new Long( s ) ;
        }
        return o ;
    }
    
    private static final boolean isUseString( String str ) {
        if( str == null || str.length() <= 0 ) {
            return false ;
        }
        int len = str.length() ;
        for( int i = 0 ; i < len ; i ++ ) {
            char c = str.charAt( i ) ;
            if( c != ' ' && c != '\t' && c != '　' ) {
                return true ;
            }
        }
        return false ;
    }
    
    private static final String trimString( String string ) {
        int s = -1 ;
        int e = -1 ;
        int len = string.length() ;
        s = 0 ;
        boolean sFlg = false ;
        for( int i = 0 ; i < len ; i ++ ) {
            char c = string.charAt( i ) ;
            if( c != ' '  && c != '　' && c != '\t' ) {
                s = i ;
                break ;
            }
            sFlg = true ;
        }
        e = len-1 ;
        boolean eFlg = false ;
        for( int i = len-1 ; i >= 0 ; i -- ) {
            char c = string.charAt( i ) ;
            if( c != ' '  && c != '　' && c != '\t' ) {
                e = i ;
                break ;
            }
            eFlg = true ;
        }
        if( sFlg == true && eFlg == true ) {
            return string.substring( s,e+1 ) ;
        }
        else if( sFlg == true ) {
            return string.substring( s ) ;
        }
        else if( eFlg == true ) {
            return string.substring( 0,e+1 ) ;
        }
        return string ;
    }
    
    private static final String _changeString(String str, String src, String dest)
            throws IllegalArgumentException {
        int pnt;
        int end;
        int srcLen;
        StringBuilder buf = null;
        if (str == null || str.length() <= 0 || src == null
                || (srcLen = src.length()) <= 0) {
            if( str == null ) {
                return "" ;
            }
            return str ;
        }
        if (dest == null || dest.length() <= 0) {
            dest = "";
        }
        buf = new StringBuilder();
        pnt = str.indexOf(src);
        if (pnt == -1) {
            return str;
        } else if (pnt == 0) {
            buf.append(dest);
            end = srcLen;
        } else {
            buf.append(str.substring(0, pnt));
            buf.append(dest);
            end = pnt + srcLen;
        }
        for (pnt += srcLen;;) {
            if ((pnt = str.indexOf(src, pnt)) == -1) {
                buf.append(str.substring(end));
                break;
            }
            if (pnt == end) {
                buf.append(dest);
            } else {
                buf.append(str.substring(end, pnt));
                buf.append(dest);
            }
            end = pnt + srcLen;
            pnt += srcLen;
        }
        return buf.toString();
    }
    
    private static final String _toString( Object o ) {
        if( o == null || o == Undefined.instance ) {
            return "" ;
        }
        if( o instanceof String ) {
            return trimString( ( String )o ) ;
        }
        return trimString( o.toString() ) ;
    }
}
