src/jdk/nashorn/api/scripting/ScriptObjectMirror.java

Wed, 23 Oct 2013 17:30:13 +0530

author
sundar
date
Wed, 23 Oct 2013 17:30:13 +0530
changeset 648
5df55690fd5b
parent 645
6f19eb443a47
child 663
98bab0cdd7bf
permissions
-rw-r--r--

8027128: jdk.nashorn.api.scripting.JSObject should be an interface
Reviewed-by: hannesw, attila, jlaskey

     1 /*
     2  * Copyright (c) 2010, 2013, 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  */
    26 package jdk.nashorn.api.scripting;
    28 import java.security.AccessControlContext;
    29 import java.security.AccessController;
    30 import java.security.Permissions;
    31 import java.security.PrivilegedAction;
    32 import java.security.ProtectionDomain;
    33 import java.util.AbstractMap;
    34 import java.util.ArrayList;
    35 import java.util.Collection;
    36 import java.util.Collections;
    37 import java.util.Iterator;
    38 import java.util.LinkedHashSet;
    39 import java.util.List;
    40 import java.util.Map;
    41 import java.util.Set;
    42 import java.util.concurrent.Callable;
    43 import javax.script.Bindings;
    44 import jdk.nashorn.internal.runtime.Context;
    45 import jdk.nashorn.internal.runtime.GlobalObject;
    46 import jdk.nashorn.internal.runtime.JSType;
    47 import jdk.nashorn.internal.runtime.ScriptFunction;
    48 import jdk.nashorn.internal.runtime.ScriptObject;
    49 import jdk.nashorn.internal.runtime.ScriptRuntime;
    51 /**
    52  * Mirror object that wraps a given Nashorn Script object.
    53  */
    54 public final class ScriptObjectMirror extends AbstractJSObject implements Bindings {
    55     private static AccessControlContext getContextAccCtxt() {
    56         final Permissions perms = new Permissions();
    57         perms.add(new RuntimePermission(Context.NASHORN_GET_CONTEXT));
    58         return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, perms) });
    59     }
    61     private static final AccessControlContext GET_CONTEXT_ACC_CTXT = getContextAccCtxt();
    63     private final ScriptObject sobj;
    64     private final ScriptObject global;
    65     private final boolean strict;
    67     @Override
    68     public boolean equals(final Object other) {
    69         if (other instanceof ScriptObjectMirror) {
    70             return sobj.equals(((ScriptObjectMirror)other).sobj);
    71         }
    73         return false;
    74     }
    76     @Override
    77     public int hashCode() {
    78         return sobj.hashCode();
    79     }
    81     @Override
    82     public String toString() {
    83         return inGlobal(new Callable<String>() {
    84             @Override
    85             public String call() {
    86                 return ScriptRuntime.safeToString(sobj);
    87             }
    88         });
    89     }
    91     // JSObject methods
    93     @Override
    94     public Object call(final Object thiz, final Object... args) {
    95         final ScriptObject oldGlobal = Context.getGlobal();
    96         final boolean globalChanged = (oldGlobal != global);
    98         try {
    99             if (globalChanged) {
   100                 Context.setGlobal(global);
   101             }
   103             if (sobj instanceof ScriptFunction) {
   104                 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
   105                 final Object self = globalChanged? wrap(thiz, oldGlobal) : thiz;
   106                 return wrap(ScriptRuntime.apply((ScriptFunction)sobj, unwrap(self, global), unwrapArray(modArgs, global)), global);
   107             }
   109             throw new RuntimeException("not a function: " + toString());
   110         } catch (final RuntimeException | Error e) {
   111             throw e;
   112         } catch (final Throwable t) {
   113             throw new RuntimeException(t);
   114         } finally {
   115             if (globalChanged) {
   116                 Context.setGlobal(oldGlobal);
   117             }
   118         }
   119     }
   121     @Override
   122     public Object newObject(final Object... args) {
   123         final ScriptObject oldGlobal = Context.getGlobal();
   124         final boolean globalChanged = (oldGlobal != global);
   126         try {
   127             if (globalChanged) {
   128                 Context.setGlobal(global);
   129             }
   131             if (sobj instanceof ScriptFunction) {
   132                 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
   133                 return wrap(ScriptRuntime.construct((ScriptFunction)sobj, unwrapArray(modArgs, global)), global);
   134             }
   136             throw new RuntimeException("not a constructor: " + toString());
   137         } catch (final RuntimeException | Error e) {
   138             throw e;
   139         } catch (final Throwable t) {
   140             throw new RuntimeException(t);
   141         } finally {
   142             if (globalChanged) {
   143                 Context.setGlobal(oldGlobal);
   144             }
   145         }
   146     }
   148     @Override
   149     public Object eval(final String s) {
   150         return inGlobal(new Callable<Object>() {
   151             @Override
   152             public Object call() {
   153                 final Context context = AccessController.doPrivileged(
   154                         new PrivilegedAction<Context>() {
   155                             @Override
   156                             public Context run() {
   157                                 return Context.getContext();
   158                             }
   159                         }, GET_CONTEXT_ACC_CTXT);
   160                 return wrap(context.eval(global, s, null, null, false), global);
   161             }
   162         });
   163     }
   165     public Object callMember(final String functionName, final Object... args) {
   166         functionName.getClass(); // null check
   167         final ScriptObject oldGlobal = Context.getGlobal();
   168         final boolean globalChanged = (oldGlobal != global);
   170         try {
   171             if (globalChanged) {
   172                 Context.setGlobal(global);
   173             }
   175             final Object val = sobj.get(functionName);
   176             if (val instanceof ScriptFunction) {
   177                 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
   178                 return wrap(ScriptRuntime.apply((ScriptFunction)val, sobj, unwrapArray(modArgs, global)), global);
   179             } else if (val instanceof JSObject && ((JSObject)val).isFunction()) {
   180                 return ((JSObject)val).call(sobj, args);
   181             }
   183             throw new NoSuchMethodException("No such function " + functionName);
   184         } catch (final RuntimeException | Error e) {
   185             throw e;
   186         } catch (final Throwable t) {
   187             throw new RuntimeException(t);
   188         } finally {
   189             if (globalChanged) {
   190                 Context.setGlobal(oldGlobal);
   191             }
   192         }
   193     }
   195     @Override
   196     public Object getMember(final String name) {
   197         name.getClass();
   198         return inGlobal(new Callable<Object>() {
   199             @Override public Object call() {
   200                 return wrap(sobj.get(name), global);
   201             }
   202         });
   203     }
   205     @Override
   206     public Object getSlot(final int index) {
   207         return inGlobal(new Callable<Object>() {
   208             @Override public Object call() {
   209                 return wrap(sobj.get(index), global);
   210             }
   211         });
   212     }
   214     @Override
   215     public boolean hasMember(final String name) {
   216         name.getClass();
   217         return inGlobal(new Callable<Boolean>() {
   218             @Override public Boolean call() {
   219                 return sobj.has(name);
   220             }
   221         });
   222     }
   224     @Override
   225     public boolean hasSlot(final int slot) {
   226         return inGlobal(new Callable<Boolean>() {
   227             @Override public Boolean call() {
   228                 return sobj.has(slot);
   229             }
   230         });
   231     }
   233     @Override
   234     public void removeMember(final String name) {
   235         name.getClass();
   236         remove(name);
   237     }
   239     @Override
   240     public void setMember(final String name, final Object value) {
   241         name.getClass();
   242         put(name, value);
   243     }
   245     @Override
   246     public void setSlot(final int index, final Object value) {
   247         inGlobal(new Callable<Void>() {
   248             @Override public Void call() {
   249                 sobj.set(index, unwrap(value, global), strict);
   250                 return null;
   251             }
   252         });
   253     }
   255     @Override
   256     public boolean isInstance(final Object obj) {
   257         if (! (obj instanceof ScriptObjectMirror)) {
   258             return false;
   259         }
   261         final ScriptObjectMirror instance = (ScriptObjectMirror)obj;
   262         // if not belongs to my global scope, return false
   263         if (global != instance.global) {
   264             return false;
   265         }
   267         return inGlobal(new Callable<Boolean>() {
   268             @Override public Boolean call() {
   269                 return sobj.isInstance(instance.sobj);
   270             }
   271         });
   272     }
   274     @Override
   275     public String getClassName() {
   276         return sobj.getClassName();
   277     }
   279     @Override
   280     public boolean isFunction() {
   281         return sobj instanceof ScriptFunction;
   282     }
   284     @Override
   285     public boolean isStrictFunction() {
   286         return isFunction() && ((ScriptFunction)sobj).isStrict();
   287     }
   289     @Override
   290     public boolean isArray() {
   291         return sobj.isArray();
   292     }
   294     // javax.script.Bindings methods
   296     @Override
   297     public void clear() {
   298         inGlobal(new Callable<Object>() {
   299             @Override public Object call() {
   300                 sobj.clear(strict);
   301                 return null;
   302             }
   303         });
   304     }
   306     @Override
   307     public boolean containsKey(final Object key) {
   308         return inGlobal(new Callable<Boolean>() {
   309             @Override public Boolean call() {
   310                 return sobj.containsKey(unwrap(key, global));
   311             }
   312         });
   313     }
   315     @Override
   316     public boolean containsValue(final Object value) {
   317         return inGlobal(new Callable<Boolean>() {
   318             @Override public Boolean call() {
   319                 return sobj.containsValue(unwrap(value, global));
   320             }
   321         });
   322     }
   324     @Override
   325     public Set<Map.Entry<String, Object>> entrySet() {
   326         return inGlobal(new Callable<Set<Map.Entry<String, Object>>>() {
   327             @Override public Set<Map.Entry<String, Object>> call() {
   328                 final Iterator<String>               iter    = sobj.propertyIterator();
   329                 final Set<Map.Entry<String, Object>> entries = new LinkedHashSet<>();
   331                 while (iter.hasNext()) {
   332                     final String key   = iter.next();
   333                     final Object value = translateUndefined(wrap(sobj.get(key), global));
   334                     entries.add(new AbstractMap.SimpleImmutableEntry<>(key, value));
   335                 }
   337                 return Collections.unmodifiableSet(entries);
   338             }
   339         });
   340     }
   342     @Override
   343     public Object get(final Object key) {
   344         return inGlobal(new Callable<Object>() {
   345             @Override public Object call() {
   346                 return translateUndefined(wrap(sobj.get(key), global));
   347             }
   348         });
   349     }
   351     @Override
   352     public boolean isEmpty() {
   353         return inGlobal(new Callable<Boolean>() {
   354             @Override public Boolean call() {
   355                 return sobj.isEmpty();
   356             }
   357         });
   358     }
   360     @Override
   361     public Set<String> keySet() {
   362         return inGlobal(new Callable<Set<String>>() {
   363             @Override public Set<String> call() {
   364                 final Iterator<String> iter   = sobj.propertyIterator();
   365                 final Set<String>      keySet = new LinkedHashSet<>();
   367                 while (iter.hasNext()) {
   368                     keySet.add(iter.next());
   369                 }
   371                 return Collections.unmodifiableSet(keySet);
   372             }
   373         });
   374     }
   376     @Override
   377     public Object put(final String key, final Object value) {
   378         final ScriptObject oldGlobal = Context.getGlobal();
   379         final boolean globalChanged = (oldGlobal != global);
   380         return inGlobal(new Callable<Object>() {
   381             @Override public Object call() {
   382                 final Object modValue = globalChanged? wrap(value, oldGlobal) : value;
   383                 return translateUndefined(wrap(sobj.put(key, unwrap(modValue, global), strict), global));
   384             }
   385         });
   386     }
   388     @Override
   389     public void putAll(final Map<? extends String, ? extends Object> map) {
   390         final ScriptObject oldGlobal = Context.getGlobal();
   391         final boolean globalChanged = (oldGlobal != global);
   392         inGlobal(new Callable<Object>() {
   393             @Override public Object call() {
   394                 for (final Map.Entry<? extends String, ? extends Object> entry : map.entrySet()) {
   395                     final Object value = entry.getValue();
   396                     final Object modValue = globalChanged? wrap(value, oldGlobal) : value;
   397                     sobj.set(entry.getKey(), unwrap(modValue, global), strict);
   398                 }
   399                 return null;
   400             }
   401         });
   402     }
   404     @Override
   405     public Object remove(final Object key) {
   406         return inGlobal(new Callable<Object>() {
   407             @Override public Object call() {
   408                 return wrap(sobj.remove(unwrap(key, global), strict), global);
   409             }
   410         });
   411     }
   413     /**
   414      * Delete a property from this object.
   415      *
   416      * @param key the property to be deleted
   417      *
   418      * @return if the delete was successful or not
   419      */
   420     public boolean delete(final Object key) {
   421         return inGlobal(new Callable<Boolean>() {
   422             @Override public Boolean call() {
   423                 return sobj.delete(unwrap(key, global), strict);
   424             }
   425         });
   426     }
   428     @Override
   429     public int size() {
   430         return inGlobal(new Callable<Integer>() {
   431             @Override public Integer call() {
   432                 return sobj.size();
   433             }
   434         });
   435     }
   437     @Override
   438     public Collection<Object> values() {
   439         return inGlobal(new Callable<Collection<Object>>() {
   440             @Override public Collection<Object> call() {
   441                 final List<Object>     values = new ArrayList<>(size());
   442                 final Iterator<Object> iter   = sobj.valueIterator();
   444                 while (iter.hasNext()) {
   445                     values.add(translateUndefined(wrap(iter.next(), global)));
   446                 }
   448                 return Collections.unmodifiableList(values);
   449             }
   450         });
   451     }
   453     // Support for ECMAScript Object API on mirrors
   455     /**
   456      * Return the __proto__ of this object.
   457      * @return __proto__ object.
   458      */
   459     public Object getProto() {
   460         return inGlobal(new Callable<Object>() {
   461             @Override public Object call() {
   462                 return wrap(sobj.getProto(), global);
   463             }
   464         });
   465     }
   467     /**
   468      * Set the __proto__ of this object.
   469      * @param proto new proto for this object
   470      */
   471     public void setProto(final Object proto) {
   472         inGlobal(new Callable<Void>() {
   473             @Override public Void call() {
   474                 sobj.setProtoCheck(unwrap(proto, global));
   475                 return null;
   476             }
   477         });
   478     }
   480     /**
   481      * ECMA 8.12.1 [[GetOwnProperty]] (P)
   482      *
   483      * @param key property key
   484      *
   485      * @return Returns the Property Descriptor of the named own property of this
   486      * object, or undefined if absent.
   487      */
   488     public Object getOwnPropertyDescriptor(final String key) {
   489         return inGlobal(new Callable<Object>() {
   490             @Override public Object call() {
   491                 return wrap(sobj.getOwnPropertyDescriptor(key), global);
   492             }
   493         });
   494     }
   496     /**
   497      * return an array of own property keys associated with the object.
   498      *
   499      * @param all True if to include non-enumerable keys.
   500      * @return Array of keys.
   501      */
   502     public String[] getOwnKeys(final boolean all) {
   503         return inGlobal(new Callable<String[]>() {
   504             @Override public String[] call() {
   505                 return sobj.getOwnKeys(all);
   506             }
   507         });
   508     }
   510     /**
   511      * Flag this script object as non extensible
   512      *
   513      * @return the object after being made non extensible
   514      */
   515     public ScriptObjectMirror preventExtensions() {
   516         return inGlobal(new Callable<ScriptObjectMirror>() {
   517             @Override public ScriptObjectMirror call() {
   518                 sobj.preventExtensions();
   519                 return ScriptObjectMirror.this;
   520             }
   521         });
   522     }
   524     /**
   525      * Check if this script object is extensible
   526      * @return true if extensible
   527      */
   528     public boolean isExtensible() {
   529         return inGlobal(new Callable<Boolean>() {
   530             @Override public Boolean call() {
   531                 return sobj.isExtensible();
   532             }
   533         });
   534     }
   536     /**
   537      * ECMAScript 15.2.3.8 - seal implementation
   538      * @return the sealed script object
   539      */
   540     public ScriptObjectMirror seal() {
   541         return inGlobal(new Callable<ScriptObjectMirror>() {
   542             @Override public ScriptObjectMirror call() {
   543                 sobj.seal();
   544                 return ScriptObjectMirror.this;
   545             }
   546         });
   547     }
   549     /**
   550      * Check whether this script object is sealed
   551      * @return true if sealed
   552      */
   553     public boolean isSealed() {
   554         return inGlobal(new Callable<Boolean>() {
   555             @Override public Boolean call() {
   556                 return sobj.isSealed();
   557             }
   558         });
   559     }
   561     /**
   562      * ECMA 15.2.39 - freeze implementation. Freeze this script object
   563      * @return the frozen script object
   564      */
   565     public ScriptObjectMirror freeze() {
   566         return inGlobal(new Callable<ScriptObjectMirror>() {
   567             @Override public ScriptObjectMirror call() {
   568                 sobj.freeze();
   569                 return ScriptObjectMirror.this;
   570             }
   571         });
   572     }
   574     /**
   575      * Check whether this script object is frozen
   576      * @return true if frozen
   577      */
   578     public boolean isFrozen() {
   579         return inGlobal(new Callable<Boolean>() {
   580             @Override public Boolean call() {
   581                 return sobj.isFrozen();
   582             }
   583         });
   584     }
   586     /**
   587      * Utility to check if given object is ECMAScript undefined value
   588      *
   589      * @param obj object to check
   590      * @return true if 'obj' is ECMAScript undefined value
   591      */
   592     public static boolean isUndefined(final Object obj) {
   593         return obj == ScriptRuntime.UNDEFINED;
   594     }
   596     /**
   597      * Make a script object mirror on given object if needed.
   598      *
   599      * @param obj object to be wrapped
   600      * @param homeGlobal global to which this object belongs
   601      * @return wrapped object
   602      */
   603     public static Object wrap(final Object obj, final ScriptObject homeGlobal) {
   604         return (obj instanceof ScriptObject && homeGlobal != null) ? new ScriptObjectMirror((ScriptObject)obj, homeGlobal) : obj;
   605     }
   607     /**
   608      * Unwrap a script object mirror if needed.
   609      *
   610      * @param obj object to be unwrapped
   611      * @param homeGlobal global to which this object belongs
   612      * @return unwrapped object
   613      */
   614     public static Object unwrap(final Object obj, final ScriptObject homeGlobal) {
   615         if (obj instanceof ScriptObjectMirror) {
   616             final ScriptObjectMirror mirror = (ScriptObjectMirror)obj;
   617             return (mirror.global == homeGlobal)? mirror.sobj : obj;
   618         }
   620         return obj;
   621     }
   623     /**
   624      * Wrap an array of object to script object mirrors if needed.
   625      *
   626      * @param args array to be unwrapped
   627      * @param homeGlobal global to which this object belongs
   628      * @return wrapped array
   629      */
   630     public static Object[] wrapArray(final Object[] args, final ScriptObject homeGlobal) {
   631         if (args == null || args.length == 0) {
   632             return args;
   633         }
   635         final Object[] newArgs = new Object[args.length];
   636         int index = 0;
   637         for (final Object obj : args) {
   638             newArgs[index] = wrap(obj, homeGlobal);
   639             index++;
   640         }
   641         return newArgs;
   642     }
   644     /**
   645      * Unwrap an array of script object mirrors if needed.
   646      *
   647      * @param args array to be unwrapped
   648      * @param homeGlobal global to which this object belongs
   649      * @return unwrapped array
   650      */
   651     public static Object[] unwrapArray(final Object[] args, final ScriptObject homeGlobal) {
   652         if (args == null || args.length == 0) {
   653             return args;
   654         }
   656         final Object[] newArgs = new Object[args.length];
   657         int index = 0;
   658         for (final Object obj : args) {
   659             newArgs[index] = unwrap(obj, homeGlobal);
   660             index++;
   661         }
   662         return newArgs;
   663     }
   665     // package-privates below this.
   667     ScriptObjectMirror(final ScriptObject sobj, final ScriptObject global) {
   668         assert sobj != null : "ScriptObjectMirror on null!";
   669         assert global instanceof GlobalObject : "global is not a GlobalObject";
   671         this.sobj = sobj;
   672         this.global = global;
   673         this.strict = ((GlobalObject)global).isStrictContext();
   674     }
   676     // accessors for script engine
   677     ScriptObject getScriptObject() {
   678         return sobj;
   679     }
   681     ScriptObject getHomeGlobal() {
   682         return global;
   683     }
   685     static Object translateUndefined(Object obj) {
   686         return (obj == ScriptRuntime.UNDEFINED)? null : obj;
   687     }
   689     // internals only below this.
   690     private <V> V inGlobal(final Callable<V> callable) {
   691         final ScriptObject oldGlobal = Context.getGlobal();
   692         final boolean globalChanged = (oldGlobal != global);
   693         if (globalChanged) {
   694             Context.setGlobal(global);
   695         }
   696         try {
   697             return callable.call();
   698         } catch (final RuntimeException e) {
   699             throw e;
   700         } catch (final Exception e) {
   701             throw new AssertionError("Cannot happen", e);
   702         } finally {
   703             if (globalChanged) {
   704                 Context.setGlobal(oldGlobal);
   705             }
   706         }
   707     }
   709     @Override
   710     public double toNumber() {
   711         return inGlobal(new Callable<Double>() {
   712             @Override public Double call() {
   713                 return JSType.toNumber(sobj);
   714             }
   715         });
   716     }
   717 }

mercurial