8066773: JSON-friendly wrapper for objects

Tue, 02 Jun 2015 10:55:17 +0200

author
attila
date
Tue, 02 Jun 2015 10:55:17 +0200
changeset 1394
07f32a26bc1e
parent 1393
dcbf5e2121e3
child 1395
fb99aafd5c0d

8066773: JSON-friendly wrapper for objects
Reviewed-by: jlaskey, lagergren, sundar

src/jdk/nashorn/api/scripting/ScriptObjectMirror.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/objects/NativeJava.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/runtime/JSONListAdapter.java file | annotate | diff | comparison | revisions
src/jdk/nashorn/internal/runtime/ListAdapter.java file | annotate | diff | comparison | revisions
test/src/jdk/nashorn/api/scripting/JSONCompatibleTest.java file | annotate | diff | comparison | revisions
     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 +}

mercurial