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 */ 25 26 package jdk.nashorn.api.scripting; 27 28 import java.security.AccessController; 29 import java.security.PrivilegedAction; 30 import java.util.AbstractMap; 31 import java.util.ArrayList; 32 import java.util.Collection; 33 import java.util.Collections; 34 import java.util.LinkedHashSet; 35 import java.util.Iterator; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Set; 39 import java.util.concurrent.Callable; 40 import javax.script.Bindings; 41 import jdk.nashorn.internal.runtime.Context; 42 import jdk.nashorn.internal.runtime.ScriptFunction; 43 import jdk.nashorn.internal.runtime.ScriptObject; 44 import jdk.nashorn.internal.runtime.ScriptRuntime; 45 46 /** 47 * Mirror object that wraps a given ScriptObject instance. User can 48 * access ScriptObject via the javax.script.Bindings interface or 49 * netscape.javascript.JSObject interface. 50 */ 51 public final class ScriptObjectMirror extends JSObject implements Bindings { 52 private final ScriptObject sobj; 53 private final ScriptObject global; 54 55 ScriptObjectMirror(final ScriptObject sobj, final ScriptObject global) { 56 this.sobj = sobj; 57 this.global = global; 58 } 59 60 @Override 61 public boolean equals(final Object other) { 62 if (other instanceof ScriptObjectMirror) { 63 return sobj.equals(((ScriptObjectMirror)other).sobj); 64 } 65 66 return false; 67 } 68 69 @Override 70 public int hashCode() { 71 return sobj.hashCode(); 72 } 73 74 @Override 75 public String toString() { 76 return inGlobal(new Callable<String>() { 77 @Override 78 public String call() { 79 return ScriptRuntime.safeToString(sobj); 80 } 81 }); 82 } 83 84 private <V> V inGlobal(final Callable<V> callable) { 85 final ScriptObject oldGlobal = NashornScriptEngine.getNashornGlobal(); 86 final boolean globalChanged = (oldGlobal != global); 87 if (globalChanged) { 88 NashornScriptEngine.setNashornGlobal(global); 89 } 90 try { 91 return callable.call(); 92 } catch (final RuntimeException e) { 93 throw e; 94 } catch (final Exception e) { 95 throw new AssertionError("Cannot happen", e); 96 } finally { 97 if (globalChanged) { 98 NashornScriptEngine.setNashornGlobal(oldGlobal); 99 } 100 } 101 } 102 103 // JSObject methods 104 @Override 105 public Object call(final String methodName, final Object args[]) { 106 final ScriptObject oldGlobal = NashornScriptEngine.getNashornGlobal(); 107 final boolean globalChanged = (oldGlobal != global); 108 109 try { 110 if (globalChanged) { 111 NashornScriptEngine.setNashornGlobal(global); 112 } 113 114 final Object val = sobj.get(methodName); 115 if (! (val instanceof ScriptFunction)) { 116 throw new RuntimeException("No such method: " + methodName); 117 } 118 119 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args; 120 return wrap(ScriptRuntime.checkAndApply((ScriptFunction)val, sobj, unwrapArray(modArgs, global)), global); 121 } catch (final RuntimeException | Error e) { 122 throw e; 123 } catch (final Throwable t) { 124 throw new RuntimeException(t); 125 } finally { 126 if (globalChanged) { 127 NashornScriptEngine.setNashornGlobal(oldGlobal); 128 } 129 } 130 } 131 132 @Override 133 public Object eval(final String s) { 134 return inGlobal(new Callable<Object>() { 135 @Override 136 public Object call() { 137 final Context context = AccessController.doPrivileged( 138 new PrivilegedAction<Context>() { 139 @Override 140 public Context run() { 141 return Context.getContext(); 142 } 143 }); 144 return wrap(context.eval(global, s, null, null, false), global); 145 } 146 }); 147 } 148 149 @Override 150 public Object getMember(final String name) { 151 return inGlobal(new Callable<Object>() { 152 @Override public Object call() { 153 return wrap(sobj.get(name), global); 154 } 155 }); 156 } 157 158 @Override 159 public Object getSlot(final int index) { 160 return inGlobal(new Callable<Object>() { 161 @Override public Object call() { 162 return wrap(sobj.get(index), global); 163 } 164 }); 165 } 166 167 @Override 168 public void removeMember(final String name) { 169 remove(name); 170 } 171 172 @Override 173 public void setMember(final String name, final Object value) { 174 put(name, value); 175 } 176 177 @Override 178 public void setSlot(final int index, final Object value) { 179 inGlobal(new Callable<Void>() { 180 @Override public Void call() { 181 sobj.set(index, unwrap(value, global), global.isStrictContext()); 182 return null; 183 } 184 }); 185 } 186 187 @Override 188 public void clear() { 189 inGlobal(new Callable<Object>() { 190 @Override public Object call() { 191 sobj.clear(); 192 return null; 193 } 194 }); 195 } 196 197 @Override 198 public boolean containsKey(final Object key) { 199 return inGlobal(new Callable<Boolean>() { 200 @Override public Boolean call() { 201 return sobj.containsKey(unwrap(key, global)); 202 } 203 }); 204 } 205 206 @Override 207 public boolean containsValue(final Object value) { 208 return inGlobal(new Callable<Boolean>() { 209 @Override public Boolean call() { 210 return sobj.containsValue(unwrap(value, global)); 211 } 212 }); 213 } 214 215 @Override 216 public Set<Map.Entry<String, Object>> entrySet() { 217 return inGlobal(new Callable<Set<Map.Entry<String, Object>>>() { 218 @Override public Set<Map.Entry<String, Object>> call() { 219 final Iterator<String> iter = sobj.propertyIterator(); 220 final Set<Map.Entry<String, Object>> entries = new LinkedHashSet<>(); 221 222 while (iter.hasNext()) { 223 final String key = iter.next(); 224 final Object value = translateUndefined(wrap(sobj.get(key), global)); 225 entries.add(new AbstractMap.SimpleImmutableEntry<>(key, value)); 226 } 227 228 return Collections.unmodifiableSet(entries); 229 } 230 }); 231 } 232 233 @Override 234 public Object get(final Object key) { 235 return inGlobal(new Callable<Object>() { 236 @Override public Object call() { 237 return translateUndefined(wrap(sobj.get(key), global)); 238 } 239 }); 240 } 241 242 @Override 243 public boolean isEmpty() { 244 return inGlobal(new Callable<Boolean>() { 245 @Override public Boolean call() { 246 return sobj.isEmpty(); 247 } 248 }); 249 } 250 251 @Override 252 public Set<String> keySet() { 253 return inGlobal(new Callable<Set<String>>() { 254 @Override public Set<String> call() { 255 final Iterator<String> iter = sobj.propertyIterator(); 256 final Set<String> keySet = new LinkedHashSet<>(); 257 258 while (iter.hasNext()) { 259 keySet.add(iter.next()); 260 } 261 262 return Collections.unmodifiableSet(keySet); 263 } 264 }); 265 } 266 267 @Override 268 public Object put(final String key, final Object value) { 269 final ScriptObject oldGlobal = NashornScriptEngine.getNashornGlobal(); 270 final boolean globalChanged = (oldGlobal != global); 271 return inGlobal(new Callable<Object>() { 272 @Override public Object call() { 273 final Object modValue = globalChanged? wrap(value, oldGlobal) : value; 274 return translateUndefined(wrap(sobj.put(key, unwrap(modValue, global)), global)); 275 } 276 }); 277 } 278 279 @Override 280 public void putAll(final Map<? extends String, ? extends Object> map) { 281 final ScriptObject oldGlobal = NashornScriptEngine.getNashornGlobal(); 282 final boolean globalChanged = (oldGlobal != global); 283 final boolean strict = sobj.isStrictContext(); 284 inGlobal(new Callable<Object>() { 285 @Override public Object call() { 286 for (final Map.Entry<? extends String, ? extends Object> entry : map.entrySet()) { 287 final Object value = entry.getValue(); 288 final Object modValue = globalChanged? wrap(value, oldGlobal) : value; 289 sobj.set(entry.getKey(), unwrap(modValue, global), strict); 290 } 291 return null; 292 } 293 }); 294 } 295 296 @Override 297 public Object remove(final Object key) { 298 return inGlobal(new Callable<Object>() { 299 @Override public Object call() { 300 return wrap(sobj.remove(unwrap(key, global)), global); 301 } 302 }); 303 } 304 305 /** 306 * Delete a property from this object. 307 * 308 * @param key the property to be deleted 309 * 310 * @return if the delete was successful or not 311 */ 312 public boolean delete(final Object key) { 313 return inGlobal(new Callable<Boolean>() { 314 @Override public Boolean call() { 315 return sobj.delete(unwrap(key, global)); 316 } 317 }); 318 } 319 320 @Override 321 public int size() { 322 return inGlobal(new Callable<Integer>() { 323 @Override public Integer call() { 324 return sobj.size(); 325 } 326 }); 327 } 328 329 @Override 330 public Collection<Object> values() { 331 return inGlobal(new Callable<Collection<Object>>() { 332 @Override public Collection<Object> call() { 333 final List<Object> values = new ArrayList<>(size()); 334 final Iterator<Object> iter = sobj.valueIterator(); 335 336 while (iter.hasNext()) { 337 values.add(translateUndefined(wrap(iter.next(), global))); 338 } 339 340 return Collections.unmodifiableList(values); 341 } 342 }); 343 } 344 345 346 // These are public only so that Context can access these. 347 348 /** 349 * Make a script object mirror on given object if needed. 350 * 351 * @param obj object to be wrapped 352 * @param homeGlobal global to which this object belongs 353 * @return wrapped object 354 */ 355 public static Object wrap(final Object obj, final ScriptObject homeGlobal) { 356 return (obj instanceof ScriptObject) ? new ScriptObjectMirror((ScriptObject)obj, homeGlobal) : obj; 357 } 358 359 /** 360 * Unwrap a script object mirror if needed. 361 * 362 * @param obj object to be unwrapped 363 * @param homeGlobal global to which this object belongs 364 * @return unwrapped object 365 */ 366 public static Object unwrap(final Object obj, final ScriptObject homeGlobal) { 367 if (obj instanceof ScriptObjectMirror) { 368 final ScriptObjectMirror mirror = (ScriptObjectMirror)obj; 369 return (mirror.global == homeGlobal)? mirror.sobj : obj; 370 } 371 372 return obj; 373 } 374 375 /** 376 * Wrap an array of object to script object mirrors if needed. 377 * 378 * @param args array to be unwrapped 379 * @param homeGlobal global to which this object belongs 380 * @return wrapped array 381 */ 382 public static Object[] wrapArray(final Object[] args, final ScriptObject homeGlobal) { 383 if (args == null || args.length == 0) { 384 return args; 385 } 386 387 final Object[] newArgs = new Object[args.length]; 388 int index = 0; 389 for (final Object obj : args) { 390 newArgs[index] = wrap(obj, homeGlobal); 391 index++; 392 } 393 return newArgs; 394 } 395 396 /** 397 * Unwrap an array of script object mirrors if needed. 398 * 399 * @param args array to be unwrapped 400 * @param homeGlobal global to which this object belongs 401 * @return unwrapped array 402 */ 403 public static Object[] unwrapArray(final Object[] args, final ScriptObject homeGlobal) { 404 if (args == null || args.length == 0) { 405 return args; 406 } 407 408 final Object[] newArgs = new Object[args.length]; 409 int index = 0; 410 for (final Object obj : args) { 411 newArgs[index] = unwrap(obj, homeGlobal); 412 index++; 413 } 414 return newArgs; 415 } 416 417 // package-privates below this. 418 ScriptObject getScriptObject() { 419 return sobj; 420 } 421 422 static Object translateUndefined(Object obj) { 423 return (obj == ScriptRuntime.UNDEFINED)? null : obj; 424 } 425 }