jdk/src/share/classes/com/sun/script/javascript/ExternalScriptable.java
changeset 17462 c1bfafc15e02
parent 17461 84860231159b
parent 17460 19eb5d62770a
child 17463 9392f1567896
equal deleted inserted replaced
17461:84860231159b 17462:c1bfafc15e02
     1 /*
       
     2  * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package com.sun.script.javascript;
       
    27 import sun.org.mozilla.javascript.internal.*;
       
    28 import javax.script.*;
       
    29 import java.util.*;
       
    30 
       
    31 /**
       
    32  * ExternalScriptable is an implementation of Scriptable
       
    33  * backed by a JSR 223 ScriptContext instance.
       
    34  *
       
    35  * @author Mike Grogan
       
    36  * @author A. Sundararajan
       
    37  * @since 1.6
       
    38  */
       
    39 
       
    40 final class ExternalScriptable implements Scriptable {
       
    41     /* Underlying ScriptContext that we use to store
       
    42      * named variables of this scope.
       
    43      */
       
    44     private ScriptContext context;
       
    45 
       
    46     /* JavaScript allows variables to be named as numbers (indexed
       
    47      * properties). This way arrays, objects (scopes) are treated uniformly.
       
    48      * Note that JSR 223 API supports only String named variables and
       
    49      * so we can't store these in Bindings. Also, JavaScript allows name
       
    50      * of the property name to be even empty String! Again, JSR 223 API
       
    51      * does not support empty name. So, we use the following fallback map
       
    52      * to store such variables of this scope. This map is not exposed to
       
    53      * JSR 223 API. We can just script objects "as is" and need not convert.
       
    54      */
       
    55     private Map<Object, Object> indexedProps;
       
    56 
       
    57     // my prototype
       
    58     private Scriptable prototype;
       
    59     // my parent scope, if any
       
    60     private Scriptable parent;
       
    61 
       
    62     ExternalScriptable(ScriptContext context) {
       
    63         this(context, new HashMap<Object, Object>());
       
    64     }
       
    65 
       
    66     ExternalScriptable(ScriptContext context, Map<Object, Object> indexedProps) {
       
    67         if (context == null) {
       
    68             throw new NullPointerException("context is null");
       
    69         }
       
    70         this.context = context;
       
    71         this.indexedProps = indexedProps;
       
    72     }
       
    73 
       
    74     ScriptContext getContext() {
       
    75         return context;
       
    76     }
       
    77 
       
    78     private boolean isEmpty(String name) {
       
    79         return name.equals("");
       
    80     }
       
    81 
       
    82     /**
       
    83      * Return the name of the class.
       
    84      */
       
    85     public String getClassName() {
       
    86         return "Global";
       
    87     }
       
    88 
       
    89     /**
       
    90      * Returns the value of the named property or NOT_FOUND.
       
    91      *
       
    92      * If the property was created using defineProperty, the
       
    93      * appropriate getter method is called.
       
    94      *
       
    95      * @param name the name of the property
       
    96      * @param start the object in which the lookup began
       
    97      * @return the value of the property (may be null), or NOT_FOUND
       
    98      */
       
    99     public synchronized Object get(String name, Scriptable start) {
       
   100         if (isEmpty(name)) {
       
   101             if (indexedProps.containsKey(name)) {
       
   102                 return indexedProps.get(name);
       
   103             } else {
       
   104                 return NOT_FOUND;
       
   105             }
       
   106         } else {
       
   107             synchronized (context) {
       
   108                 int scope = context.getAttributesScope(name);
       
   109                 if (scope != -1) {
       
   110                     Object value = context.getAttribute(name, scope);
       
   111                     return Context.javaToJS(value, this);
       
   112                 } else {
       
   113                     return NOT_FOUND;
       
   114                 }
       
   115             }
       
   116         }
       
   117     }
       
   118 
       
   119     /**
       
   120      * Returns the value of the indexed property or NOT_FOUND.
       
   121      *
       
   122      * @param index the numeric index for the property
       
   123      * @param start the object in which the lookup began
       
   124      * @return the value of the property (may be null), or NOT_FOUND
       
   125      */
       
   126     public synchronized Object get(int index, Scriptable start) {
       
   127         Integer key = new Integer(index);
       
   128         if (indexedProps.containsKey(index)) {
       
   129             return indexedProps.get(key);
       
   130         } else {
       
   131             return NOT_FOUND;
       
   132         }
       
   133     }
       
   134 
       
   135     /**
       
   136      * Returns true if the named property is defined.
       
   137      *
       
   138      * @param name the name of the property
       
   139      * @param start the object in which the lookup began
       
   140      * @return true if and only if the property was found in the object
       
   141      */
       
   142     public synchronized boolean has(String name, Scriptable start) {
       
   143         if (isEmpty(name)) {
       
   144             return indexedProps.containsKey(name);
       
   145         } else {
       
   146             synchronized (context) {
       
   147                 return context.getAttributesScope(name) != -1;
       
   148             }
       
   149         }
       
   150     }
       
   151 
       
   152     /**
       
   153      * Returns true if the property index is defined.
       
   154      *
       
   155      * @param index the numeric index for the property
       
   156      * @param start the object in which the lookup began
       
   157      * @return true if and only if the property was found in the object
       
   158      */
       
   159     public synchronized boolean has(int index, Scriptable start) {
       
   160         Integer key = new Integer(index);
       
   161         return indexedProps.containsKey(key);
       
   162     }
       
   163 
       
   164     /**
       
   165      * Sets the value of the named property, creating it if need be.
       
   166      *
       
   167      * @param name the name of the property
       
   168      * @param start the object whose property is being set
       
   169      * @param value value to set the property to
       
   170      */
       
   171     public void put(String name, Scriptable start, Object value) {
       
   172         if (start == this) {
       
   173             synchronized (this) {
       
   174                 if (isEmpty(name)) {
       
   175                     indexedProps.put(name, value);
       
   176                 } else {
       
   177                     synchronized (context) {
       
   178                         int scope = context.getAttributesScope(name);
       
   179                         if (scope == -1) {
       
   180                             scope = ScriptContext.ENGINE_SCOPE;
       
   181                         }
       
   182                         context.setAttribute(name, jsToJava(value), scope);
       
   183                     }
       
   184                 }
       
   185             }
       
   186         } else {
       
   187             start.put(name, start, value);
       
   188         }
       
   189     }
       
   190 
       
   191     /**
       
   192      * Sets the value of the indexed property, creating it if need be.
       
   193      *
       
   194      * @param index the numeric index for the property
       
   195      * @param start the object whose property is being set
       
   196      * @param value value to set the property to
       
   197      */
       
   198     public void put(int index, Scriptable start, Object value) {
       
   199         if (start == this) {
       
   200             synchronized (this) {
       
   201                 indexedProps.put(new Integer(index), value);
       
   202             }
       
   203         } else {
       
   204             start.put(index, start, value);
       
   205         }
       
   206     }
       
   207 
       
   208     /**
       
   209      * Removes a named property from the object.
       
   210      *
       
   211      * If the property is not found, no action is taken.
       
   212      *
       
   213      * @param name the name of the property
       
   214      */
       
   215     public synchronized void delete(String name) {
       
   216         if (isEmpty(name)) {
       
   217             indexedProps.remove(name);
       
   218         } else {
       
   219             synchronized (context) {
       
   220                 int scope = context.getAttributesScope(name);
       
   221                 if (scope != -1) {
       
   222                     context.removeAttribute(name, scope);
       
   223                 }
       
   224             }
       
   225         }
       
   226     }
       
   227 
       
   228     /**
       
   229      * Removes the indexed property from the object.
       
   230      *
       
   231      * If the property is not found, no action is taken.
       
   232      *
       
   233      * @param index the numeric index for the property
       
   234      */
       
   235     public void delete(int index) {
       
   236         indexedProps.remove(new Integer(index));
       
   237     }
       
   238 
       
   239     /**
       
   240      * Get the prototype of the object.
       
   241      * @return the prototype
       
   242      */
       
   243     public Scriptable getPrototype() {
       
   244         return prototype;
       
   245     }
       
   246 
       
   247     /**
       
   248      * Set the prototype of the object.
       
   249      * @param prototype the prototype to set
       
   250      */
       
   251     public void setPrototype(Scriptable prototype) {
       
   252         this.prototype = prototype;
       
   253     }
       
   254 
       
   255     /**
       
   256      * Get the parent scope of the object.
       
   257      * @return the parent scope
       
   258      */
       
   259     public Scriptable getParentScope() {
       
   260         return parent;
       
   261     }
       
   262 
       
   263     /**
       
   264      * Set the parent scope of the object.
       
   265      * @param parent the parent scope to set
       
   266      */
       
   267     public void setParentScope(Scriptable parent) {
       
   268         this.parent = parent;
       
   269     }
       
   270 
       
   271      /**
       
   272      * Get an array of property ids.
       
   273      *
       
   274      * Not all property ids need be returned. Those properties
       
   275      * whose ids are not returned are considered non-enumerable.
       
   276      *
       
   277      * @return an array of Objects. Each entry in the array is either
       
   278      *         a java.lang.String or a java.lang.Number
       
   279      */
       
   280     public synchronized Object[] getIds() {
       
   281         String[] keys = getAllKeys();
       
   282         int size = keys.length + indexedProps.size();
       
   283         Object[] res = new Object[size];
       
   284         System.arraycopy(keys, 0, res, 0, keys.length);
       
   285         int i = keys.length;
       
   286         // now add all indexed properties
       
   287         for (Object index : indexedProps.keySet()) {
       
   288             res[i++] = index;
       
   289         }
       
   290         return res;
       
   291     }
       
   292 
       
   293     /**
       
   294      * Get the default value of the object with a given hint.
       
   295      * The hints are String.class for type String, Number.class for type
       
   296      * Number, Scriptable.class for type Object, and Boolean.class for
       
   297      * type Boolean. <p>
       
   298      *
       
   299      * A <code>hint</code> of null means "no hint".
       
   300      *
       
   301      * See ECMA 8.6.2.6.
       
   302      *
       
   303      * @param hint the type hint
       
   304      * @return the default value
       
   305      */
       
   306     public Object getDefaultValue(Class typeHint) {
       
   307         for (int i=0; i < 2; i++) {
       
   308             boolean tryToString;
       
   309             if (typeHint == ScriptRuntime.StringClass) {
       
   310                 tryToString = (i == 0);
       
   311             } else {
       
   312                 tryToString = (i == 1);
       
   313             }
       
   314 
       
   315             String methodName;
       
   316             Object[] args;
       
   317             if (tryToString) {
       
   318                 methodName = "toString";
       
   319                 args = ScriptRuntime.emptyArgs;
       
   320             } else {
       
   321                 methodName = "valueOf";
       
   322                 args = new Object[1];
       
   323                 String hint;
       
   324                 if (typeHint == null) {
       
   325                     hint = "undefined";
       
   326                 } else if (typeHint == ScriptRuntime.StringClass) {
       
   327                     hint = "string";
       
   328                 } else if (typeHint == ScriptRuntime.ScriptableClass) {
       
   329                     hint = "object";
       
   330                 } else if (typeHint == ScriptRuntime.FunctionClass) {
       
   331                     hint = "function";
       
   332                 } else if (typeHint == ScriptRuntime.BooleanClass
       
   333                            || typeHint == Boolean.TYPE)
       
   334                 {
       
   335                     hint = "boolean";
       
   336                 } else if (typeHint == ScriptRuntime.NumberClass ||
       
   337                          typeHint == ScriptRuntime.ByteClass ||
       
   338                          typeHint == Byte.TYPE ||
       
   339                          typeHint == ScriptRuntime.ShortClass ||
       
   340                          typeHint == Short.TYPE ||
       
   341                          typeHint == ScriptRuntime.IntegerClass ||
       
   342                          typeHint == Integer.TYPE ||
       
   343                          typeHint == ScriptRuntime.FloatClass ||
       
   344                          typeHint == Float.TYPE ||
       
   345                          typeHint == ScriptRuntime.DoubleClass ||
       
   346                          typeHint == Double.TYPE)
       
   347                 {
       
   348                     hint = "number";
       
   349                 } else {
       
   350                     throw Context.reportRuntimeError(
       
   351                         "Invalid JavaScript value of type " +
       
   352                         typeHint.toString());
       
   353                 }
       
   354                 args[0] = hint;
       
   355             }
       
   356             Object v = ScriptableObject.getProperty(this, methodName);
       
   357             if (!(v instanceof Function))
       
   358                 continue;
       
   359             Function fun = (Function) v;
       
   360             Context cx = RhinoScriptEngine.enterContext();
       
   361             try {
       
   362                 v = fun.call(cx, fun.getParentScope(), this, args);
       
   363             } finally {
       
   364                 cx.exit();
       
   365             }
       
   366             if (v != null) {
       
   367                 if (!(v instanceof Scriptable)) {
       
   368                     return v;
       
   369                 }
       
   370                 if (typeHint == ScriptRuntime.ScriptableClass
       
   371                     || typeHint == ScriptRuntime.FunctionClass)
       
   372                 {
       
   373                     return v;
       
   374                 }
       
   375                 if (tryToString && v instanceof Wrapper) {
       
   376                     // Let a wrapped java.lang.String pass for a primitive
       
   377                     // string.
       
   378                     Object u = ((Wrapper)v).unwrap();
       
   379                     if (u instanceof String)
       
   380                         return u;
       
   381                 }
       
   382             }
       
   383         }
       
   384         // fall through to error
       
   385         String arg = (typeHint == null) ? "undefined" : typeHint.getName();
       
   386         throw Context.reportRuntimeError(
       
   387                   "Cannot find default value for object " + arg);
       
   388     }
       
   389 
       
   390     /**
       
   391      * Implements the instanceof operator.
       
   392      *
       
   393      * @param instance The value that appeared on the LHS of the instanceof
       
   394      *              operator
       
   395      * @return true if "this" appears in value's prototype chain
       
   396      *
       
   397      */
       
   398     public boolean hasInstance(Scriptable instance) {
       
   399         // Default for JS objects (other than Function) is to do prototype
       
   400         // chasing.
       
   401         Scriptable proto = instance.getPrototype();
       
   402         while (proto != null) {
       
   403             if (proto.equals(this)) return true;
       
   404             proto = proto.getPrototype();
       
   405         }
       
   406         return false;
       
   407     }
       
   408 
       
   409     private String[] getAllKeys() {
       
   410         ArrayList<String> list = new ArrayList<String>();
       
   411         synchronized (context) {
       
   412             for (int scope : context.getScopes()) {
       
   413                 Bindings bindings = context.getBindings(scope);
       
   414                 if (bindings != null) {
       
   415                     list.ensureCapacity(bindings.size());
       
   416                     for (String key : bindings.keySet()) {
       
   417                         list.add(key);
       
   418                     }
       
   419                 }
       
   420             }
       
   421         }
       
   422         String[] res = new String[list.size()];
       
   423         list.toArray(res);
       
   424         return res;
       
   425     }
       
   426 
       
   427    /**
       
   428     * We convert script values to the nearest Java value.
       
   429     * We unwrap wrapped Java objects so that access from
       
   430     * Bindings.get() would return "workable" value for Java.
       
   431     * But, at the same time, we need to make few special cases
       
   432     * and hence the following function is used.
       
   433     */
       
   434     private Object jsToJava(Object jsObj) {
       
   435         if (jsObj instanceof Wrapper) {
       
   436             Wrapper njb = (Wrapper) jsObj;
       
   437             /* importClass feature of ImporterTopLevel puts
       
   438              * NativeJavaClass in global scope. If we unwrap
       
   439              * it, importClass won't work.
       
   440              */
       
   441             if (njb instanceof NativeJavaClass) {
       
   442                 return njb;
       
   443             }
       
   444 
       
   445             /* script may use Java primitive wrapper type objects
       
   446              * (such as java.lang.Integer, java.lang.Boolean etc)
       
   447              * explicitly. If we unwrap, then these script objects
       
   448              * will become script primitive types. For example,
       
   449              *
       
   450              *    var x = new java.lang.Double(3.0); print(typeof x);
       
   451              *
       
   452              * will print 'number'. We don't want that to happen.
       
   453              */
       
   454             Object obj = njb.unwrap();
       
   455             if (obj instanceof Number || obj instanceof String ||
       
   456                 obj instanceof Boolean || obj instanceof Character) {
       
   457                 // special type wrapped -- we just leave it as is.
       
   458                 return njb;
       
   459             } else {
       
   460                 // return unwrapped object for any other object.
       
   461                 return obj;
       
   462             }
       
   463         } else { // not-a-Java-wrapper
       
   464             return jsObj;
       
   465         }
       
   466     }
       
   467 }