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

changeset 1394
07f32a26bc1e
parent 1250
9ee1fc3f6136
child 1399
22640d19073c
     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  }

mercurial