1.1 --- a/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java Wed Jun 03 10:42:06 2015 +0200 1.2 +++ b/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java Tue Jun 02 10:55:17 2015 +0200 1.3 @@ -47,6 +47,7 @@ 1.4 import jdk.nashorn.internal.runtime.ConsString; 1.5 import jdk.nashorn.internal.runtime.Context; 1.6 import jdk.nashorn.internal.runtime.ECMAException; 1.7 +import jdk.nashorn.internal.runtime.JSONListAdapter; 1.8 import jdk.nashorn.internal.runtime.JSType; 1.9 import jdk.nashorn.internal.runtime.ScriptFunction; 1.10 import jdk.nashorn.internal.runtime.ScriptObject; 1.11 @@ -72,6 +73,7 @@ 1.12 private final ScriptObject sobj; 1.13 private final Global global; 1.14 private final boolean strict; 1.15 + private final boolean jsonCompatible; 1.16 1.17 @Override 1.18 public boolean equals(final Object other) { 1.19 @@ -110,9 +112,9 @@ 1.20 } 1.21 1.22 if (sobj instanceof ScriptFunction) { 1.23 - final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args; 1.24 - final Object self = globalChanged? wrap(thiz, oldGlobal) : thiz; 1.25 - return wrap(ScriptRuntime.apply((ScriptFunction)sobj, unwrap(self, global), unwrapArray(modArgs, global)), global); 1.26 + final Object[] modArgs = globalChanged? wrapArrayLikeMe(args, oldGlobal) : args; 1.27 + final Object self = globalChanged? wrapLikeMe(thiz, oldGlobal) : thiz; 1.28 + return wrapLikeMe(ScriptRuntime.apply((ScriptFunction)sobj, unwrap(self, global), unwrapArray(modArgs, global))); 1.29 } 1.30 1.31 throw new RuntimeException("not a function: " + toString()); 1.32 @@ -140,8 +142,8 @@ 1.33 } 1.34 1.35 if (sobj instanceof ScriptFunction) { 1.36 - final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args; 1.37 - return wrap(ScriptRuntime.construct((ScriptFunction)sobj, unwrapArray(modArgs, global)), global); 1.38 + final Object[] modArgs = globalChanged? wrapArrayLikeMe(args, oldGlobal) : args; 1.39 + return wrapLikeMe(ScriptRuntime.construct((ScriptFunction)sobj, unwrapArray(modArgs, global))); 1.40 } 1.41 1.42 throw new RuntimeException("not a constructor: " + toString()); 1.43 @@ -170,7 +172,7 @@ 1.44 return Context.getContext(); 1.45 } 1.46 }, GET_CONTEXT_ACC_CTXT); 1.47 - return wrap(context.eval(global, s, sobj, null, false), global); 1.48 + return wrapLikeMe(context.eval(global, s, sobj, null, false)); 1.49 } 1.50 }); 1.51 } 1.52 @@ -193,8 +195,8 @@ 1.53 1.54 final Object val = sobj.get(functionName); 1.55 if (val instanceof ScriptFunction) { 1.56 - final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args; 1.57 - return wrap(ScriptRuntime.apply((ScriptFunction)val, sobj, unwrapArray(modArgs, global)), global); 1.58 + final Object[] modArgs = globalChanged? wrapArrayLikeMe(args, oldGlobal) : args; 1.59 + return wrapLikeMe(ScriptRuntime.apply((ScriptFunction)val, sobj, unwrapArray(modArgs, global))); 1.60 } else if (val instanceof JSObject && ((JSObject)val).isFunction()) { 1.61 return ((JSObject)val).call(sobj, args); 1.62 } 1.63 @@ -218,7 +220,7 @@ 1.64 Objects.requireNonNull(name); 1.65 return inGlobal(new Callable<Object>() { 1.66 @Override public Object call() { 1.67 - return wrap(sobj.get(name), global); 1.68 + return wrapLikeMe(sobj.get(name)); 1.69 } 1.70 }); 1.71 } 1.72 @@ -227,7 +229,7 @@ 1.73 public Object getSlot(final int index) { 1.74 return inGlobal(new Callable<Object>() { 1.75 @Override public Object call() { 1.76 - return wrap(sobj.get(index), global); 1.77 + return wrapLikeMe(sobj.get(index)); 1.78 } 1.79 }); 1.80 } 1.81 @@ -368,7 +370,7 @@ 1.82 1.83 while (iter.hasNext()) { 1.84 final String key = iter.next(); 1.85 - final Object value = translateUndefined(wrap(sobj.get(key), global)); 1.86 + final Object value = translateUndefined(wrapLikeMe(sobj.get(key))); 1.87 entries.add(new AbstractMap.SimpleImmutableEntry<>(key, value)); 1.88 } 1.89 1.90 @@ -382,7 +384,7 @@ 1.91 checkKey(key); 1.92 return inGlobal(new Callable<Object>() { 1.93 @Override public Object call() { 1.94 - return translateUndefined(wrap(sobj.get(key), global)); 1.95 + return translateUndefined(wrapLikeMe(sobj.get(key))); 1.96 } 1.97 }); 1.98 } 1.99 @@ -419,8 +421,8 @@ 1.100 final boolean globalChanged = (oldGlobal != global); 1.101 return inGlobal(new Callable<Object>() { 1.102 @Override public Object call() { 1.103 - final Object modValue = globalChanged? wrap(value, oldGlobal) : value; 1.104 - return translateUndefined(wrap(sobj.put(key, unwrap(modValue, global), strict), global)); 1.105 + final Object modValue = globalChanged? wrapLikeMe(value, oldGlobal) : value; 1.106 + return translateUndefined(wrapLikeMe(sobj.put(key, unwrap(modValue, global), strict))); 1.107 } 1.108 }); 1.109 } 1.110 @@ -434,7 +436,7 @@ 1.111 @Override public Object call() { 1.112 for (final Map.Entry<? extends String, ? extends Object> entry : map.entrySet()) { 1.113 final Object value = entry.getValue(); 1.114 - final Object modValue = globalChanged? wrap(value, oldGlobal) : value; 1.115 + final Object modValue = globalChanged? wrapLikeMe(value, oldGlobal) : value; 1.116 final String key = entry.getKey(); 1.117 checkKey(key); 1.118 sobj.set(key, unwrap(modValue, global), getCallSiteFlags()); 1.119 @@ -449,7 +451,7 @@ 1.120 checkKey(key); 1.121 return inGlobal(new Callable<Object>() { 1.122 @Override public Object call() { 1.123 - return translateUndefined(wrap(sobj.remove(key, strict), global)); 1.124 + return translateUndefined(wrapLikeMe(sobj.remove(key, strict))); 1.125 } 1.126 }); 1.127 } 1.128 @@ -486,7 +488,7 @@ 1.129 final Iterator<Object> iter = sobj.valueIterator(); 1.130 1.131 while (iter.hasNext()) { 1.132 - values.add(translateUndefined(wrap(iter.next(), global))); 1.133 + values.add(translateUndefined(wrapLikeMe(iter.next()))); 1.134 } 1.135 1.136 return Collections.unmodifiableList(values); 1.137 @@ -503,7 +505,7 @@ 1.138 public Object getProto() { 1.139 return inGlobal(new Callable<Object>() { 1.140 @Override public Object call() { 1.141 - return wrap(sobj.getProto(), global); 1.142 + return wrapLikeMe(sobj.getProto()); 1.143 } 1.144 }); 1.145 } 1.146 @@ -532,7 +534,7 @@ 1.147 public Object getOwnPropertyDescriptor(final String key) { 1.148 return inGlobal(new Callable<Object>() { 1.149 @Override public Object call() { 1.150 - return wrap(sobj.getOwnPropertyDescriptor(key), global); 1.151 + return wrapLikeMe(sobj.getOwnPropertyDescriptor(key)); 1.152 } 1.153 }); 1.154 } 1.155 @@ -661,16 +663,76 @@ 1.156 * @return wrapped/converted object 1.157 */ 1.158 public static Object wrap(final Object obj, final Object homeGlobal) { 1.159 + return wrap(obj, homeGlobal, false); 1.160 + } 1.161 + 1.162 + /** 1.163 + * Make a script object mirror on given object if needed. Also converts ConsString instances to Strings. The 1.164 + * created wrapper will implement the Java {@code List} interface if {@code obj} is a JavaScript 1.165 + * {@code Array} object; this is compatible with Java JSON libraries expectations. Arrays retrieved through its 1.166 + * properties (transitively) will also implement the list interface. 1.167 + * 1.168 + * @param obj object to be wrapped/converted 1.169 + * @param homeGlobal global to which this object belongs. Not used for ConsStrings. 1.170 + * @return wrapped/converted object 1.171 + */ 1.172 + public static Object wrapAsJSONCompatible(final Object obj, final Object homeGlobal) { 1.173 + return wrap(obj, homeGlobal, true); 1.174 + } 1.175 + 1.176 + /** 1.177 + * Make a script object mirror on given object if needed. Also converts ConsString instances to Strings. 1.178 + * 1.179 + * @param obj object to be wrapped/converted 1.180 + * @param homeGlobal global to which this object belongs. Not used for ConsStrings. 1.181 + * @param jsonCompatible if true, the created wrapper will implement the Java {@code List} interface if 1.182 + * {@code obj} is a JavaScript {@code Array} object. Arrays retrieved through its properties (transitively) 1.183 + * will also implement the list interface. 1.184 + * @return wrapped/converted object 1.185 + */ 1.186 + private static Object wrap(final Object obj, final Object homeGlobal, final boolean jsonCompatible) { 1.187 if(obj instanceof ScriptObject) { 1.188 - return homeGlobal instanceof Global ? new ScriptObjectMirror((ScriptObject)obj, (Global)homeGlobal) : obj; 1.189 - } 1.190 - if(obj instanceof ConsString) { 1.191 + if (!(homeGlobal instanceof Global)) { 1.192 + return obj; 1.193 + } 1.194 + final ScriptObject sobj = (ScriptObject)obj; 1.195 + final Global global = (Global)homeGlobal; 1.196 + final ScriptObjectMirror mirror = new ScriptObjectMirror(sobj, global, jsonCompatible); 1.197 + if (jsonCompatible && sobj.isArray()) { 1.198 + return new JSONListAdapter(mirror, global); 1.199 + } 1.200 + return mirror; 1.201 + } else if(obj instanceof ConsString) { 1.202 return obj.toString(); 1.203 + } else if (jsonCompatible && obj instanceof ScriptObjectMirror) { 1.204 + // Since choosing JSON compatible representation is an explicit decision on user's part, if we're asked to 1.205 + // wrap a mirror that was not JSON compatible, explicitly create its compatible counterpart following the 1.206 + // principle of least surprise. 1.207 + return ((ScriptObjectMirror)obj).asJSONCompatible(); 1.208 } 1.209 return obj; 1.210 } 1.211 1.212 /** 1.213 + * Wraps the passed object with the same jsonCompatible flag as this mirror. 1.214 + * @param obj the object 1.215 + * @param homeGlobal the object's home global. 1.216 + * @return a wrapper for the object. 1.217 + */ 1.218 + private Object wrapLikeMe(final Object obj, final Object homeGlobal) { 1.219 + return wrap(obj, homeGlobal, jsonCompatible); 1.220 + } 1.221 + 1.222 + /** 1.223 + * Wraps the passed object with the same home global and jsonCompatible flag as this mirror. 1.224 + * @param obj the object 1.225 + * @return a wrapper for the object. 1.226 + */ 1.227 + private Object wrapLikeMe(final Object obj) { 1.228 + return wrapLikeMe(obj, global); 1.229 + } 1.230 + 1.231 + /** 1.232 * Unwrap a script object mirror if needed. 1.233 * 1.234 * @param obj object to be unwrapped 1.235 @@ -681,6 +743,8 @@ 1.236 if (obj instanceof ScriptObjectMirror) { 1.237 final ScriptObjectMirror mirror = (ScriptObjectMirror)obj; 1.238 return (mirror.global == homeGlobal)? mirror.sobj : obj; 1.239 + } else if (obj instanceof JSONListAdapter) { 1.240 + return ((JSONListAdapter)obj).unwrap(homeGlobal); 1.241 } 1.242 1.243 return obj; 1.244 @@ -694,6 +758,10 @@ 1.245 * @return wrapped array 1.246 */ 1.247 public static Object[] wrapArray(final Object[] args, final Object homeGlobal) { 1.248 + return wrapArray(args, homeGlobal, false); 1.249 + } 1.250 + 1.251 + private static Object[] wrapArray(final Object[] args, final Object homeGlobal, final boolean jsonCompatible) { 1.252 if (args == null || args.length == 0) { 1.253 return args; 1.254 } 1.255 @@ -701,12 +769,16 @@ 1.256 final Object[] newArgs = new Object[args.length]; 1.257 int index = 0; 1.258 for (final Object obj : args) { 1.259 - newArgs[index] = wrap(obj, homeGlobal); 1.260 + newArgs[index] = wrap(obj, homeGlobal, jsonCompatible); 1.261 index++; 1.262 } 1.263 return newArgs; 1.264 } 1.265 1.266 + private Object[] wrapArrayLikeMe(final Object[] args, final Object homeGlobal) { 1.267 + return wrapArray(args, homeGlobal, jsonCompatible); 1.268 + } 1.269 + 1.270 /** 1.271 * Unwrap an array of script object mirrors if needed. 1.272 * 1.273 @@ -748,12 +820,17 @@ 1.274 // package-privates below this. 1.275 1.276 ScriptObjectMirror(final ScriptObject sobj, final Global global) { 1.277 + this(sobj, global, false); 1.278 + } 1.279 + 1.280 + private ScriptObjectMirror(final ScriptObject sobj, final Global global, final boolean jsonCompatible) { 1.281 assert sobj != null : "ScriptObjectMirror on null!"; 1.282 assert global != null : "home Global is null"; 1.283 1.284 this.sobj = sobj; 1.285 this.global = global; 1.286 this.strict = global.isStrictContext(); 1.287 + this.jsonCompatible = jsonCompatible; 1.288 } 1.289 1.290 // accessors for script engine 1.291 @@ -838,4 +915,11 @@ 1.292 } 1.293 }); 1.294 } 1.295 + 1.296 + private ScriptObjectMirror asJSONCompatible() { 1.297 + if (this.jsonCompatible) { 1.298 + return this; 1.299 + } 1.300 + return new ScriptObjectMirror(sobj, global, true); 1.301 + } 1.302 }