Tue, 02 Jun 2015 10:55:17 +0200
8066773: JSON-friendly wrapper for objects
Reviewed-by: jlaskey, lagergren, sundar
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 }
2.1 --- a/src/jdk/nashorn/internal/objects/NativeJava.java Wed Jun 03 10:42:06 2015 +0200 2.2 +++ b/src/jdk/nashorn/internal/objects/NativeJava.java Tue Jun 02 10:55:17 2015 +0200 2.3 @@ -33,10 +33,12 @@ 2.4 import java.util.Collection; 2.5 import java.util.Deque; 2.6 import java.util.List; 2.7 +import java.util.Map; 2.8 import java.util.Queue; 2.9 import jdk.internal.dynalink.beans.StaticClass; 2.10 import jdk.internal.dynalink.support.TypeUtilities; 2.11 import jdk.nashorn.api.scripting.JSObject; 2.12 +import jdk.nashorn.api.scripting.ScriptObjectMirror; 2.13 import jdk.nashorn.internal.objects.annotations.Attribute; 2.14 import jdk.nashorn.internal.objects.annotations.Function; 2.15 import jdk.nashorn.internal.objects.annotations.ScriptClass; 2.16 @@ -656,4 +658,20 @@ 2.17 public static Object _super(final Object self, final Object adapter) { 2.18 return Bootstrap.createSuperAdapter(adapter); 2.19 } 2.20 + 2.21 + /** 2.22 + * Returns an object that is compatible with Java JSON libraries expectations; namely, that if it itself, or any 2.23 + * object transitively reachable through it is a JavaScript array, then such objects will be exposed as 2.24 + * {@link JSObject} that also implements the {@link List} interface for exposing the array elements. An explicit 2.25 + * API is required as otherwise Nashorn exposes all objects externally as {@link JSObject}s that also implement the 2.26 + * {@link Map} interface instead. By using this method, arrays will be exposed as {@link List}s and all other 2.27 + * objects as {@link Map}s. 2.28 + * @param self not used 2.29 + * @param obj the object to be exposed in a Java JSON library compatible manner. 2.30 + * @return a wrapper around the object that will enforce Java JSON library compatible exposure. 2.31 + */ 2.32 + @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR) 2.33 + public static Object asJSONCompatible(final Object self, final Object obj) { 2.34 + return ScriptObjectMirror.wrapAsJSONCompatible(obj, Context.getGlobal()); 2.35 + } 2.36 }
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/src/jdk/nashorn/internal/runtime/JSONListAdapter.java Tue Jun 02 10:55:17 2015 +0200 3.3 @@ -0,0 +1,156 @@ 3.4 +/* 3.5 + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 3.7 + * 3.8 + * This code is free software; you can redistribute it and/or modify it 3.9 + * under the terms of the GNU General Public License version 2 only, as 3.10 + * published by the Free Software Foundation. Oracle designates this 3.11 + * particular file as subject to the "Classpath" exception as provided 3.12 + * by Oracle in the LICENSE file that accompanied this code. 3.13 + * 3.14 + * This code is distributed in the hope that it will be useful, but WITHOUT 3.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 3.16 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 3.17 + * version 2 for more details (a copy is included in the LICENSE file that 3.18 + * accompanied this code). 3.19 + * 3.20 + * You should have received a copy of the GNU General Public License version 3.21 + * 2 along with this work; if not, write to the Free Software Foundation, 3.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 3.23 + * 3.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 3.25 + * or visit www.oracle.com if you need additional information or have any 3.26 + * questions. 3.27 + */ 3.28 + 3.29 +package jdk.nashorn.internal.runtime; 3.30 + 3.31 +import java.util.Collection; 3.32 +import java.util.List; 3.33 +import java.util.Set; 3.34 +import jdk.nashorn.api.scripting.JSObject; 3.35 +import jdk.nashorn.api.scripting.ScriptObjectMirror; 3.36 +import jdk.nashorn.internal.objects.Global; 3.37 + 3.38 +/** 3.39 + * A {@link ListAdapter} that also implements {@link JSObject}. Named {@code JSONListAdapter} as it is used as a 3.40 + * {@code JSObject} implementing the {@link List} interface, which is the expected interface to be implemented by 3.41 + * JSON-parsed arrays when they are handled in Java. We aren't implementing {@link JSObject} on {@link ListAdapter} 3.42 + * directly since that'd have implications for other uses of list adapter (e.g. interferences of JSObject default 3.43 + * value calculation vs. List's {@code toString()} etc.) 3.44 + */ 3.45 +public final class JSONListAdapter extends ListAdapter implements JSObject { 3.46 + /** 3.47 + * Creates a new JSON list adapter. 3.48 + * @param obj the underlying object being exposed as a list. 3.49 + * @param global the home global of the underlying object. 3.50 + */ 3.51 + public JSONListAdapter(final JSObject obj, final Global global) { 3.52 + super(obj, global); 3.53 + } 3.54 + 3.55 + /** 3.56 + * Unwraps this adapter into its underlying non-JSObject representative. 3.57 + * @param homeGlobal the home global for unwrapping 3.58 + * @return either the unwrapped object or this if it should not be unwrapped in the specified global. 3.59 + */ 3.60 + public Object unwrap(final Object homeGlobal) { 3.61 + final Object unwrapped = ScriptObjectMirror.unwrap(obj, homeGlobal); 3.62 + return unwrapped != obj ? unwrapped : this; 3.63 + } 3.64 + 3.65 + @Override 3.66 + public Object call(final Object thiz, final Object... args) { 3.67 + return obj.call(thiz, args); 3.68 + } 3.69 + 3.70 + @Override 3.71 + public Object newObject(final Object... args) { 3.72 + return obj.newObject(args); 3.73 + } 3.74 + 3.75 + @Override 3.76 + public Object eval(final String s) { 3.77 + return obj.eval(s); 3.78 + } 3.79 + 3.80 + @Override 3.81 + public Object getMember(final String name) { 3.82 + return obj.getMember(name); 3.83 + } 3.84 + 3.85 + @Override 3.86 + public Object getSlot(final int index) { 3.87 + return obj.getSlot(index); 3.88 + } 3.89 + 3.90 + @Override 3.91 + public boolean hasMember(final String name) { 3.92 + return obj.hasMember(name); 3.93 + } 3.94 + 3.95 + @Override 3.96 + public boolean hasSlot(final int slot) { 3.97 + return obj.hasSlot(slot); 3.98 + } 3.99 + 3.100 + @Override 3.101 + public void removeMember(final String name) { 3.102 + obj.removeMember(name); 3.103 + } 3.104 + 3.105 + @Override 3.106 + public void setMember(final String name, final Object value) { 3.107 + obj.setMember(name, value); 3.108 + } 3.109 + 3.110 + @Override 3.111 + public void setSlot(final int index, final Object value) { 3.112 + obj.setSlot(index, value); 3.113 + } 3.114 + 3.115 + @Override 3.116 + public Set<String> keySet() { 3.117 + return obj.keySet(); 3.118 + } 3.119 + 3.120 + @Override 3.121 + public Collection<Object> values() { 3.122 + return obj.values(); 3.123 + } 3.124 + 3.125 + @Override 3.126 + public boolean isInstance(final Object instance) { 3.127 + return obj.isInstance(instance); 3.128 + } 3.129 + 3.130 + @Override 3.131 + public boolean isInstanceOf(final Object clazz) { 3.132 + return obj.isInstanceOf(clazz); 3.133 + } 3.134 + 3.135 + @Override 3.136 + public String getClassName() { 3.137 + return obj.getClassName(); 3.138 + } 3.139 + 3.140 + @Override 3.141 + public boolean isFunction() { 3.142 + return obj.isFunction(); 3.143 + } 3.144 + 3.145 + @Override 3.146 + public boolean isStrictFunction() { 3.147 + return obj.isStrictFunction(); 3.148 + } 3.149 + 3.150 + @Override 3.151 + public boolean isArray() { 3.152 + return obj.isArray(); 3.153 + } 3.154 + 3.155 + @Override @Deprecated 3.156 + public double toNumber() { 3.157 + return obj.toNumber(); 3.158 + } 3.159 +}
4.1 --- a/src/jdk/nashorn/internal/runtime/ListAdapter.java Wed Jun 03 10:42:06 2015 +0200 4.2 +++ b/src/jdk/nashorn/internal/runtime/ListAdapter.java Tue Jun 02 10:55:17 2015 +0200 4.3 @@ -52,7 +52,7 @@ 4.4 * operations respectively, while {@link #addLast(Object)} and {@link #removeLast()} will translate to {@code push} and 4.5 * {@code pop}. 4.6 */ 4.7 -public final class ListAdapter extends AbstractList<Object> implements RandomAccess, Deque<Object> { 4.8 +public class ListAdapter extends AbstractList<Object> implements RandomAccess, Deque<Object> { 4.9 // Invoker creator for methods that add to the start or end of the list: PUSH and UNSHIFT. Takes fn, this, and value, returns void. 4.10 private static final Callable<MethodHandle> ADD_INVOKER_CREATOR = invokerCreator(void.class, Object.class, JSObject.class, Object.class); 4.11 4.12 @@ -78,21 +78,17 @@ 4.13 private static final Callable<MethodHandle> SPLICE_REMOVE_INVOKER_CREATOR = invokerCreator(void.class, Object.class, JSObject.class, int.class, int.class); 4.14 4.15 /** wrapped object */ 4.16 - private final JSObject obj; 4.17 + final JSObject obj; 4.18 private final Global global; 4.19 4.20 // allow subclasses only in this package 4.21 - ListAdapter(final JSObject obj) { 4.22 + ListAdapter(final JSObject obj, final Global global) { 4.23 + if (global == null) { 4.24 + throw new IllegalStateException(ECMAErrors.getMessage("list.adapter.null.global")); 4.25 + } 4.26 + 4.27 this.obj = obj; 4.28 - this.global = getGlobalNonNull(); 4.29 - } 4.30 - 4.31 - private static Global getGlobalNonNull() { 4.32 - final Global global = Context.getGlobal(); 4.33 - if (global != null) { 4.34 - return global; 4.35 - } 4.36 - throw new IllegalStateException(ECMAErrors.getMessage("list.adapter.null.global")); 4.37 + this.global = global; 4.38 } 4.39 4.40 /** 4.41 @@ -102,12 +98,13 @@ 4.42 * @return A ListAdapter wrapper object 4.43 */ 4.44 public static ListAdapter create(final Object obj) { 4.45 - return new ListAdapter(getJSObject(obj)); 4.46 + final Global global = Context.getGlobal(); 4.47 + return new ListAdapter(getJSObject(obj, global), global); 4.48 } 4.49 4.50 - private static JSObject getJSObject(final Object obj) { 4.51 + private static JSObject getJSObject(final Object obj, final Global global) { 4.52 if (obj instanceof ScriptObject) { 4.53 - return (JSObject)ScriptObjectMirror.wrap(obj, Context.getGlobal()); 4.54 + return (JSObject)ScriptObjectMirror.wrap(obj, global); 4.55 } else if (obj instanceof JSObject) { 4.56 return (JSObject)obj; 4.57 }
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 5.2 +++ b/test/src/jdk/nashorn/api/scripting/JSONCompatibleTest.java Tue Jun 02 10:55:17 2015 +0200 5.3 @@ -0,0 +1,115 @@ 5.4 +/* 5.5 + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 5.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5.7 + * 5.8 + * This code is free software; you can redistribute it and/or modify it 5.9 + * under the terms of the GNU General Public License version 2 only, as 5.10 + * published by the Free Software Foundation. Oracle designates this 5.11 + * particular file as subject to the "Classpath" exception as provided 5.12 + * by Oracle in the LICENSE file that accompanied this code. 5.13 + * 5.14 + * This code is distributed in the hope that it will be useful, but WITHOUT 5.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 5.16 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 5.17 + * version 2 for more details (a copy is included in the LICENSE file that 5.18 + * accompanied this code). 5.19 + * 5.20 + * You should have received a copy of the GNU General Public License version 5.21 + * 2 along with this work; if not, write to the Free Software Foundation, 5.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 5.23 + * 5.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 5.25 + * or visit www.oracle.com if you need additional information or have any 5.26 + * questions. 5.27 + */ 5.28 + 5.29 +package jdk.nashorn.api.scripting; 5.30 + 5.31 +import static org.testng.Assert.assertEquals; 5.32 +import static org.testng.Assert.assertTrue; 5.33 + 5.34 +import java.util.Arrays; 5.35 +import java.util.List; 5.36 +import java.util.Map; 5.37 +import javax.script.ScriptEngine; 5.38 +import javax.script.ScriptException; 5.39 +import org.testng.Assert; 5.40 +import org.testng.annotations.Test; 5.41 + 5.42 +public class JSONCompatibleTest { 5.43 + 5.44 + /** 5.45 + * Wrap a top-level array as a list. 5.46 + */ 5.47 + @Test 5.48 + public void testWrapArray() throws ScriptException { 5.49 + final ScriptEngine engine = new NashornScriptEngineFactory().getScriptEngine(); 5.50 + final Object val = engine.eval("Java.asJSONCompatible([1, 2, 3])"); 5.51 + assertEquals(asList(val), Arrays.asList(1, 2, 3)); 5.52 + } 5.53 + 5.54 + /** 5.55 + * Wrap an embedded array as a list. 5.56 + */ 5.57 + @Test 5.58 + public void testWrapObjectWithArray() throws ScriptException { 5.59 + final ScriptEngine engine = new NashornScriptEngineFactory().getScriptEngine(); 5.60 + final Object val = engine.eval("Java.asJSONCompatible({x: [1, 2, 3]})"); 5.61 + assertEquals(asList(asMap(val).get("x")), Arrays.asList(1, 2, 3)); 5.62 + } 5.63 + 5.64 + /** 5.65 + * Check it all works transitively several more levels down. 5.66 + */ 5.67 + @Test 5.68 + public void testDeepWrapping() throws ScriptException { 5.69 + final ScriptEngine engine = new NashornScriptEngineFactory().getScriptEngine(); 5.70 + final Object val = engine.eval("Java.asJSONCompatible({x: [1, {y: [2, {z: [3]}]}, [4, 5]]})"); 5.71 + final Map<String, Object> root = asMap(val); 5.72 + final List<Object> x = asList(root.get("x")); 5.73 + assertEquals(x.get(0), 1); 5.74 + final Map<String, Object> x1 = asMap(x.get(1)); 5.75 + final List<Object> y = asList(x1.get("y")); 5.76 + assertEquals(y.get(0), 2); 5.77 + final Map<String, Object> y1 = asMap(y.get(1)); 5.78 + assertEquals(asList(y1.get("z")), Arrays.asList(3)); 5.79 + assertEquals(asList(x.get(2)), Arrays.asList(4, 5)); 5.80 + } 5.81 + 5.82 + /** 5.83 + * Ensure that the old behaviour (every object is a Map) is unchanged. 5.84 + */ 5.85 + @Test 5.86 + public void testNonWrapping() throws ScriptException { 5.87 + final ScriptEngine engine = new NashornScriptEngineFactory().getScriptEngine(); 5.88 + final Object val = engine.eval("({x: [1, {y: [2, {z: [3]}]}, [4, 5]]})"); 5.89 + final Map<String, Object> root = asMap(val); 5.90 + final Map<String, Object> x = asMap(root.get("x")); 5.91 + assertEquals(x.get("0"), 1); 5.92 + final Map<String, Object> x1 = asMap(x.get("1")); 5.93 + final Map<String, Object> y = asMap(x1.get("y")); 5.94 + assertEquals(y.get("0"), 2); 5.95 + final Map<String, Object> y1 = asMap(y.get("1")); 5.96 + final Map<String, Object> z = asMap(y1.get("z")); 5.97 + assertEquals(z.get("0"), 3); 5.98 + final Map<String, Object> x2 = asMap(x.get("2")); 5.99 + assertEquals(x2.get("0"), 4); 5.100 + assertEquals(x2.get("1"), 5); 5.101 + } 5.102 + 5.103 + private static List<Object> asList(final Object obj) { 5.104 + assertJSObject(obj); 5.105 + Assert.assertTrue(obj instanceof List); 5.106 + return (List)obj; 5.107 + } 5.108 + 5.109 + private static Map<String, Object> asMap(final Object obj) { 5.110 + assertJSObject(obj); 5.111 + Assert.assertTrue(obj instanceof Map); 5.112 + return (Map)obj; 5.113 + } 5.114 + 5.115 + private static void assertJSObject(final Object obj) { 5.116 + assertTrue(obj instanceof JSObject); 5.117 + } 5.118 +}