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