/*
 * 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, A. Sundararajan
Since:1.6
/** * 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.
/** * 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.
Params:
  • name – the name of the property
  • start – the object in which the lookup began
Returns:the value of the property (may be null), or NOT_FOUND
/** * 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.
Params:
  • index – the numeric index for the property
  • start – the object in which the lookup began
Returns:the value of the property (may be null), or 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.
Params:
  • name – the name of the property
  • start – the object in which the lookup began
Returns:true if and only if the property was found in the object
/** * 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.
Params:
  • index – the numeric index for the property
  • start – the object in which the lookup began
Returns:true if and only if the property was found in the object
/** * 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.
Params:
  • name – the name of the property
  • start – the object whose property is being set
  • value – value to set the property to
/** * 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.
Params:
  • index – the numeric index for the property
  • start – the object whose property is being set
  • value – value to set the property to
/** * 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.
Params:
  • name – the name of the property
/** * 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.
Params:
  • index – the numeric index for the property
/** * 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.
Returns:the prototype
/** * Get the prototype of the object. * @return the prototype */
public Scriptable getPrototype() { return prototype; }
Set the prototype of the object.
Params:
  • prototype – the prototype to set
/** * 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.
Returns:the parent scope
/** * Get the parent scope of the object. * @return the parent scope */
public Scriptable getParentScope() { return parent; }
Set the parent scope of the object.
Params:
  • parent – the parent scope to set
/** * 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.
Returns:an array of Objects. Each entry in the array is either a java.lang.String or a java.lang.Number
/** * 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.

A hint of null means "no hint". See ECMA 8.6.2.6.

Params:
  • hint – the type hint
Returns:the default value
/** * 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.
Params:
  • instance – The value that appeared on the LHS of the instanceof operator
Returns:true if "this" appears in value's prototype chain
/** * 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.
/** * 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; } } }