467 lines · 15.7 KB · Java
RawDownload
/* * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.script.javascript;import sun.org.mozilla.javascript.internal.*;import javax.script.*;import java.util.*; /** * ExternalScriptable is an implementation of Scriptable * backed by a JSR 223 ScriptContext instance. * * @author Mike Grogan * @author A. Sundararajan * @since 1.6 */ final class ExternalScriptable implements Scriptable {    /* Underlying ScriptContext that we use to store     * named variables of this scope.     */    private ScriptContext context;     /* JavaScript allows variables to be named as numbers (indexed     * properties). This way arrays, objects (scopes) are treated uniformly.     * Note that JSR 223 API supports only String named variables and     * so we can't store these in Bindings. Also, JavaScript allows name     * of the property name to be even empty String! Again, JSR 223 API     * does not support empty name. So, we use the following fallback map     * to store such variables of this scope. This map is not exposed to     * JSR 223 API. We can just script objects "as is" and need not convert.     */    private Map<Object, Object> indexedProps;     // my prototype    private Scriptable prototype;    // my parent scope, if any    private Scriptable parent;     ExternalScriptable(ScriptContext context) {        this(context, new HashMap<Object, Object>());    }     ExternalScriptable(ScriptContext context, Map<Object, Object> indexedProps) {        if (context == null) {            throw new NullPointerException("context is null");        }        this.context = context;        this.indexedProps = indexedProps;    }     ScriptContext getContext() {        return context;    }     private boolean isEmpty(String name) {        return name.equals("");    }     /**     * Return the name of the class.     */    public String getClassName() {        return "Global";    }     /**     * Returns the value of the named property or NOT_FOUND.     *     * If the property was created using defineProperty, the     * appropriate getter method is called.     *     * @param name the name of the property     * @param start the object in which the lookup began     * @return the value of the property (may be null), or NOT_FOUND     */    public synchronized Object get(String name, Scriptable start) {        if (isEmpty(name)) {            if (indexedProps.containsKey(name)) {                return indexedProps.get(name);            } else {                return NOT_FOUND;            }        } else {            synchronized (context) {                int scope = context.getAttributesScope(name);                if (scope != -1) {                    Object value = context.getAttribute(name, scope);                    return Context.javaToJS(value, this);                } else {                    return NOT_FOUND;                }            }        }    }     /**     * Returns the value of the indexed property or NOT_FOUND.     *     * @param index the numeric index for the property     * @param start the object in which the lookup began     * @return the value of the property (may be null), or NOT_FOUND     */    public synchronized Object get(int index, Scriptable start) {        Integer key = new Integer(index);        if (indexedProps.containsKey(index)) {            return indexedProps.get(key);        } else {            return NOT_FOUND;        }    }     /**     * Returns true if the named property is defined.     *     * @param name the name of the property     * @param start the object in which the lookup began     * @return true if and only if the property was found in the object     */    public synchronized boolean has(String name, Scriptable start) {        if (isEmpty(name)) {            return indexedProps.containsKey(name);        } else {            synchronized (context) {                return context.getAttributesScope(name) != -1;            }        }    }     /**     * Returns true if the property index is defined.     *     * @param index the numeric index for the property     * @param start the object in which the lookup began     * @return true if and only if the property was found in the object     */    public synchronized boolean has(int index, Scriptable start) {        Integer key = new Integer(index);        return indexedProps.containsKey(key);    }     /**     * Sets the value of the named property, creating it if need be.     *     * @param name the name of the property     * @param start the object whose property is being set     * @param value value to set the property to     */    public void put(String name, Scriptable start, Object value) {        if (start == this) {            synchronized (this) {                if (isEmpty(name)) {                    indexedProps.put(name, value);                } else {                    synchronized (context) {                        int scope = context.getAttributesScope(name);                        if (scope == -1) {                            scope = ScriptContext.ENGINE_SCOPE;                        }                        context.setAttribute(name, jsToJava(value), scope);                    }                }            }        } else {            start.put(name, start, value);        }    }     /**     * Sets the value of the indexed property, creating it if need be.     *     * @param index the numeric index for the property     * @param start the object whose property is being set     * @param value value to set the property to     */    public void put(int index, Scriptable start, Object value) {        if (start == this) {            synchronized (this) {                indexedProps.put(new Integer(index), value);            }        } else {            start.put(index, start, value);        }    }     /**     * Removes a named property from the object.     *     * If the property is not found, no action is taken.     *     * @param name the name of the property     */    public synchronized void delete(String name) {        if (isEmpty(name)) {            indexedProps.remove(name);        } else {            synchronized (context) {                int scope = context.getAttributesScope(name);                if (scope != -1) {                    context.removeAttribute(name, scope);                }            }        }    }     /**     * Removes the indexed property from the object.     *     * If the property is not found, no action is taken.     *     * @param index the numeric index for the property     */    public void delete(int index) {        indexedProps.remove(new Integer(index));    }     /**     * Get the prototype of the object.     * @return the prototype     */    public Scriptable getPrototype() {        return prototype;    }     /**     * Set the prototype of the object.     * @param prototype the prototype to set     */    public void setPrototype(Scriptable prototype) {        this.prototype = prototype;    }     /**     * Get the parent scope of the object.     * @return the parent scope     */    public Scriptable getParentScope() {        return parent;    }     /**     * Set the parent scope of the object.     * @param parent the parent scope to set     */    public void setParentScope(Scriptable parent) {        this.parent = parent;    }      /**     * Get an array of property ids.     *     * Not all property ids need be returned. Those properties     * whose ids are not returned are considered non-enumerable.     *     * @return an array of Objects. Each entry in the array is either     *         a java.lang.String or a java.lang.Number     */    public synchronized Object[] getIds() {        String[] keys = getAllKeys();        int size = keys.length + indexedProps.size();        Object[] res = new Object[size];        System.arraycopy(keys, 0, res, 0, keys.length);        int i = keys.length;        // now add all indexed properties        for (Object index : indexedProps.keySet()) {            res[i++] = index;        }        return res;    }     /**     * Get the default value of the object with a given hint.     * The hints are String.class for type String, Number.class for type     * Number, Scriptable.class for type Object, and Boolean.class for     * type Boolean. <p>     *     * A <code>hint</code> of null means "no hint".     *     * See ECMA 8.6.2.6.     *     * @param hint the type hint     * @return the default value     */    public Object getDefaultValue(Class typeHint) {        for (int i=0; i < 2; i++) {            boolean tryToString;            if (typeHint == ScriptRuntime.StringClass) {                tryToString = (i == 0);            } else {                tryToString = (i == 1);            }             String methodName;            Object[] args;            if (tryToString) {                methodName = "toString";                args = ScriptRuntime.emptyArgs;            } else {                methodName = "valueOf";                args = new Object[1];                String hint;                if (typeHint == null) {                    hint = "undefined";                } else if (typeHint == ScriptRuntime.StringClass) {                    hint = "string";                } else if (typeHint == ScriptRuntime.ScriptableClass) {                    hint = "object";                } else if (typeHint == ScriptRuntime.FunctionClass) {                    hint = "function";                } else if (typeHint == ScriptRuntime.BooleanClass                           || typeHint == Boolean.TYPE)                {                    hint = "boolean";                } else if (typeHint == ScriptRuntime.NumberClass ||                         typeHint == ScriptRuntime.ByteClass ||                         typeHint == Byte.TYPE ||                         typeHint == ScriptRuntime.ShortClass ||                         typeHint == Short.TYPE ||                         typeHint == ScriptRuntime.IntegerClass ||                         typeHint == Integer.TYPE ||                         typeHint == ScriptRuntime.FloatClass ||                         typeHint == Float.TYPE ||                         typeHint == ScriptRuntime.DoubleClass ||                         typeHint == Double.TYPE)                {                    hint = "number";                } else {                    throw Context.reportRuntimeError(                        "Invalid JavaScript value of type " +                        typeHint.toString());                }                args[0] = hint;            }            Object v = ScriptableObject.getProperty(this, methodName);            if (!(v instanceof Function))                continue;            Function fun = (Function) v;            Context cx = RhinoScriptEngine.enterContext();            try {                v = fun.call(cx, fun.getParentScope(), this, args);            } finally {                cx.exit();            }            if (v != null) {                if (!(v instanceof Scriptable)) {                    return v;                }                if (typeHint == ScriptRuntime.ScriptableClass                    || typeHint == ScriptRuntime.FunctionClass)                {                    return v;                }                if (tryToString && v instanceof Wrapper) {                    // Let a wrapped java.lang.String pass for a primitive                    // string.                    Object u = ((Wrapper)v).unwrap();                    if (u instanceof String)                        return u;                }            }        }        // fall through to error        String arg = (typeHint == null) ? "undefined" : typeHint.getName();        throw Context.reportRuntimeError(                  "Cannot find default value for object " + arg);    }     /**     * Implements the instanceof operator.     *     * @param instance The value that appeared on the LHS of the instanceof     *              operator     * @return true if "this" appears in value's prototype chain     *     */    public boolean hasInstance(Scriptable instance) {        // Default for JS objects (other than Function) is to do prototype        // chasing.        Scriptable proto = instance.getPrototype();        while (proto != null) {            if (proto.equals(this)) return true;            proto = proto.getPrototype();        }        return false;    }     private String[] getAllKeys() {        ArrayList<String> list = new ArrayList<String>();        synchronized (context) {            for (int scope : context.getScopes()) {                Bindings bindings = context.getBindings(scope);                if (bindings != null) {                    list.ensureCapacity(bindings.size());                    for (String key : bindings.keySet()) {                        list.add(key);                    }                }            }        }        String[] res = new String[list.size()];        list.toArray(res);        return res;    }    /**    * 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.    */    private 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;        }    }}