src/jdk/nashorn/internal/objects/NativeJSON.java

Mon, 22 Sep 2014 13:28:28 +0200

author
hannesw
date
Mon, 22 Sep 2014 13:28:28 +0200
changeset 1020
9ee8fd4a7266
parent 962
ac62e33a99b0
child 1205
4112748288bb
child 1251
85a6a7545dbe
permissions
-rw-r--r--

8047764: Indexed or polymorphic set on global affects Object.prototype
Reviewed-by: lagergren, attila

jlaskey@3 1 /*
jlaskey@7 2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
jlaskey@3 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
jlaskey@3 4 *
jlaskey@3 5 * This code is free software; you can redistribute it and/or modify it
jlaskey@3 6 * under the terms of the GNU General Public License version 2 only, as
jlaskey@3 7 * published by the Free Software Foundation. Oracle designates this
jlaskey@3 8 * particular file as subject to the "Classpath" exception as provided
jlaskey@3 9 * by Oracle in the LICENSE file that accompanied this code.
jlaskey@3 10 *
jlaskey@3 11 * This code is distributed in the hope that it will be useful, but WITHOUT
jlaskey@3 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
jlaskey@3 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
jlaskey@3 14 * version 2 for more details (a copy is included in the LICENSE file that
jlaskey@3 15 * accompanied this code).
jlaskey@3 16 *
jlaskey@3 17 * You should have received a copy of the GNU General Public License version
jlaskey@3 18 * 2 along with this work; if not, write to the Free Software Foundation,
jlaskey@3 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
jlaskey@3 20 *
jlaskey@3 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
jlaskey@3 22 * or visit www.oracle.com if you need additional information or have any
jlaskey@3 23 * questions.
jlaskey@3 24 */
jlaskey@3 25
jlaskey@3 26 package jdk.nashorn.internal.objects;
jlaskey@3 27
jlaskey@3 28 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
jlaskey@3 29 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
jlaskey@3 30
jlaskey@3 31 import java.lang.invoke.MethodHandle;
jlaskey@3 32 import java.util.ArrayList;
jlaskey@3 33 import java.util.Arrays;
jlaskey@3 34 import java.util.IdentityHashMap;
jlaskey@3 35 import java.util.Iterator;
jlaskey@3 36 import java.util.List;
jlaskey@3 37 import java.util.Map;
sundar@489 38 import java.util.concurrent.Callable;
jlaskey@3 39 import jdk.nashorn.internal.objects.annotations.Attribute;
jlaskey@3 40 import jdk.nashorn.internal.objects.annotations.Function;
jlaskey@3 41 import jdk.nashorn.internal.objects.annotations.ScriptClass;
jlaskey@3 42 import jdk.nashorn.internal.objects.annotations.Where;
jlaskey@3 43 import jdk.nashorn.internal.runtime.ConsString;
sundar@82 44 import jdk.nashorn.internal.runtime.JSONFunctions;
jlaskey@3 45 import jdk.nashorn.internal.runtime.JSType;
hannesw@380 46 import jdk.nashorn.internal.runtime.PropertyMap;
jlaskey@3 47 import jdk.nashorn.internal.runtime.ScriptFunction;
jlaskey@3 48 import jdk.nashorn.internal.runtime.ScriptObject;
jlaskey@3 49 import jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator;
jlaskey@3 50 import jdk.nashorn.internal.runtime.linker.Bootstrap;
jlaskey@3 51 import jdk.nashorn.internal.runtime.linker.InvokeByName;
jlaskey@3 52
jlaskey@3 53 /**
jlaskey@3 54 * ECMAScript 262 Edition 5, Section 15.12 The NativeJSON Object
jlaskey@3 55 *
jlaskey@3 56 */
jlaskey@3 57 @ScriptClass("JSON")
jlaskey@3 58 public final class NativeJSON extends ScriptObject {
sundar@489 59 private static final Object TO_JSON = new Object();
sundar@489 60
sundar@489 61 private static InvokeByName getTO_JSON() {
sundar@489 62 return Global.instance().getInvokeByName(TO_JSON,
sundar@489 63 new Callable<InvokeByName>() {
sundar@489 64 @Override
sundar@489 65 public InvokeByName call() {
sundar@489 66 return new InvokeByName("toJSON", ScriptObject.class, Object.class, Object.class);
sundar@489 67 }
sundar@489 68 });
sundar@489 69 }
sundar@489 70
sundar@489 71
sundar@489 72 private static final Object REPLACER_INVOKER = new Object();
sundar@489 73
sundar@489 74 private static MethodHandle getREPLACER_INVOKER() {
sundar@489 75 return Global.instance().getDynamicInvoker(REPLACER_INVOKER,
sundar@489 76 new Callable<MethodHandle>() {
sundar@489 77 @Override
sundar@489 78 public MethodHandle call() {
sundar@489 79 return Bootstrap.createDynamicInvoker("dyn:call", Object.class,
sundar@489 80 ScriptFunction.class, ScriptObject.class, Object.class, Object.class);
sundar@489 81 }
sundar@489 82 });
sundar@489 83 }
jlaskey@3 84
hannesw@380 85 // initialized by nasgen
sundar@418 86 @SuppressWarnings("unused")
hannesw@380 87 private static PropertyMap $nasgenmap$;
jlaskey@3 88
sundar@414 89 private NativeJSON() {
sundar@414 90 // don't create me!!
sundar@414 91 throw new UnsupportedOperationException();
jlaskey@3 92 }
jlaskey@3 93
jlaskey@3 94 /**
jlaskey@3 95 * ECMA 15.12.2 parse ( text [ , reviver ] )
jlaskey@3 96 *
jlaskey@3 97 * @param self self reference
jlaskey@3 98 * @param text a JSON formatted string
jlaskey@3 99 * @param reviver optional value: function that takes two parameters (key, value)
jlaskey@3 100 *
jlaskey@3 101 * @return an ECMA script value
jlaskey@3 102 */
jlaskey@3 103 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
jlaskey@3 104 public static Object parse(final Object self, final Object text, final Object reviver) {
sundar@82 105 return JSONFunctions.parse(text, reviver);
jlaskey@3 106 }
jlaskey@3 107
jlaskey@3 108 /**
jlaskey@3 109 * ECMA 15.12.3 stringify ( value [ , replacer [ , space ] ] )
jlaskey@3 110 *
jlaskey@3 111 * @param self self reference
jlaskey@3 112 * @param value ECMA script value (usually object or array)
jlaskey@3 113 * @param replacer either a function or an array of strings and numbers
jlaskey@3 114 * @param space optional parameter - allows result to have whitespace injection
jlaskey@3 115 *
jlaskey@3 116 * @return a string in JSON format
jlaskey@3 117 */
jlaskey@3 118 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
jlaskey@3 119 public static Object stringify(final Object self, final Object value, final Object replacer, final Object space) {
jlaskey@3 120 // The stringify method takes a value and an optional replacer, and an optional
jlaskey@3 121 // space parameter, and returns a JSON text. The replacer can be a function
jlaskey@3 122 // that can replace values, or an array of strings that will select the keys.
jlaskey@3 123
jlaskey@3 124 // A default replacer method can be provided. Use of the space parameter can
jlaskey@3 125 // produce text that is more easily readable.
jlaskey@3 126
jlaskey@3 127 final StringifyState state = new StringifyState();
jlaskey@3 128
jlaskey@3 129 // If there is a replacer, it must be a function or an array.
jlaskey@3 130 if (replacer instanceof ScriptFunction) {
jlaskey@3 131 state.replacerFunction = (ScriptFunction) replacer;
jlaskey@3 132 } else if (isArray(replacer) ||
jlaskey@3 133 replacer instanceof Iterable ||
jlaskey@3 134 (replacer != null && replacer.getClass().isArray())) {
jlaskey@3 135
jlaskey@3 136 state.propertyList = new ArrayList<>();
jlaskey@3 137
jlaskey@3 138 final Iterator<Object> iter = ArrayLikeIterator.arrayLikeIterator(replacer);
jlaskey@3 139
jlaskey@3 140 while (iter.hasNext()) {
jlaskey@3 141 String item = null;
jlaskey@3 142 final Object v = iter.next();
jlaskey@3 143
jlaskey@3 144 if (v instanceof String) {
jlaskey@3 145 item = (String) v;
jlaskey@3 146 } else if (v instanceof ConsString) {
jlaskey@3 147 item = v.toString();
jlaskey@3 148 } else if (v instanceof Number ||
jlaskey@3 149 v instanceof NativeNumber ||
jlaskey@3 150 v instanceof NativeString) {
jlaskey@3 151 item = JSType.toString(v);
jlaskey@3 152 }
jlaskey@3 153
jlaskey@3 154 if (item != null) {
jlaskey@3 155 state.propertyList.add(item);
jlaskey@3 156 }
jlaskey@3 157 }
jlaskey@3 158 }
jlaskey@3 159
jlaskey@3 160 // If the space parameter is a number, make an indent
jlaskey@3 161 // string containing that many spaces.
jlaskey@3 162
jlaskey@3 163 String gap;
jlaskey@3 164
sundar@569 165 // modifiable 'space' - parameter is final
sundar@569 166 Object modSpace = space;
sundar@569 167 if (modSpace instanceof NativeNumber) {
sundar@569 168 modSpace = JSType.toNumber(JSType.toPrimitive(modSpace, Number.class));
sundar@569 169 } else if (modSpace instanceof NativeString) {
sundar@569 170 modSpace = JSType.toString(JSType.toPrimitive(modSpace, String.class));
sundar@569 171 }
sundar@569 172
sundar@569 173 if (modSpace instanceof Number) {
attila@962 174 final int indent = Math.min(10, JSType.toInteger(modSpace));
sundar@569 175 if (indent < 1) {
sundar@569 176 gap = "";
jlaskey@3 177 } else {
sundar@569 178 final StringBuilder sb = new StringBuilder();
sundar@569 179 for (int i = 0; i < indent; i++) {
sundar@569 180 sb.append(' ');
sundar@569 181 }
sundar@569 182 gap = sb.toString();
jlaskey@3 183 }
sundar@569 184 } else if (modSpace instanceof String || modSpace instanceof ConsString) {
sundar@569 185 final String str = modSpace.toString();
jlaskey@3 186 gap = str.substring(0, Math.min(10, str.length()));
jlaskey@3 187 } else {
jlaskey@3 188 gap = "";
jlaskey@3 189 }
jlaskey@3 190
jlaskey@3 191 state.gap = gap;
jlaskey@3 192
jlaskey@3 193 final ScriptObject wrapper = Global.newEmptyInstance();
hannesw@1020 194 wrapper.set("", value, 0);
jlaskey@3 195
jlaskey@3 196 return str("", wrapper, state);
jlaskey@3 197 }
jlaskey@3 198
jlaskey@3 199 // -- Internals only below this point
jlaskey@3 200
jlaskey@3 201 // stringify helpers.
jlaskey@3 202
jlaskey@3 203 private static class StringifyState {
jlaskey@3 204 final Map<ScriptObject, ScriptObject> stack = new IdentityHashMap<>();
jlaskey@3 205
jlaskey@3 206 StringBuilder indent = new StringBuilder();
jlaskey@3 207 String gap = "";
jlaskey@3 208 List<String> propertyList = null;
jlaskey@3 209 ScriptFunction replacerFunction = null;
jlaskey@3 210 }
jlaskey@3 211
jlaskey@3 212 // Spec: The abstract operation Str(key, holder).
jlaskey@3 213 private static Object str(final Object key, final ScriptObject holder, final StringifyState state) {
jlaskey@3 214 Object value = holder.get(key);
jlaskey@3 215
jlaskey@3 216 try {
jlaskey@3 217 if (value instanceof ScriptObject) {
sundar@489 218 final InvokeByName toJSONInvoker = getTO_JSON();
jlaskey@3 219 final ScriptObject svalue = (ScriptObject)value;
sundar@489 220 final Object toJSON = toJSONInvoker.getGetter().invokeExact(svalue);
sundar@459 221 if (Bootstrap.isCallable(toJSON)) {
sundar@489 222 value = toJSONInvoker.getInvoker().invokeExact(toJSON, svalue, key);
jlaskey@3 223 }
jlaskey@3 224 }
jlaskey@3 225
jlaskey@3 226 if (state.replacerFunction != null) {
sundar@489 227 value = getREPLACER_INVOKER().invokeExact(state.replacerFunction, holder, key, value);
jlaskey@3 228 }
jlaskey@3 229 } catch(Error|RuntimeException t) {
jlaskey@3 230 throw t;
jlaskey@3 231 } catch(final Throwable t) {
jlaskey@3 232 throw new RuntimeException(t);
jlaskey@3 233 }
jlaskey@3 234 final boolean isObj = (value instanceof ScriptObject);
jlaskey@3 235 if (isObj) {
jlaskey@3 236 if (value instanceof NativeNumber) {
jlaskey@3 237 value = JSType.toNumber(value);
jlaskey@3 238 } else if (value instanceof NativeString) {
jlaskey@3 239 value = JSType.toString(value);
jlaskey@3 240 } else if (value instanceof NativeBoolean) {
jlaskey@3 241 value = ((NativeBoolean)value).booleanValue();
jlaskey@3 242 }
jlaskey@3 243 }
jlaskey@3 244
jlaskey@3 245 if (value == null) {
jlaskey@3 246 return "null";
jlaskey@3 247 } else if (Boolean.TRUE.equals(value)) {
jlaskey@3 248 return "true";
jlaskey@3 249 } else if (Boolean.FALSE.equals(value)) {
jlaskey@3 250 return "false";
jlaskey@3 251 }
jlaskey@3 252
jlaskey@3 253 if (value instanceof String) {
sundar@82 254 return JSONFunctions.quote((String)value);
jlaskey@3 255 } else if (value instanceof ConsString) {
sundar@82 256 return JSONFunctions.quote(value.toString());
jlaskey@3 257 }
jlaskey@3 258
jlaskey@3 259 if (value instanceof Number) {
jlaskey@3 260 return JSType.isFinite(((Number)value).doubleValue()) ? JSType.toString(value) : "null";
jlaskey@3 261 }
jlaskey@3 262
jlaskey@3 263 final JSType type = JSType.of(value);
jlaskey@3 264 if (type == JSType.OBJECT) {
jlaskey@3 265 if (isArray(value)) {
hannesw@223 266 return JA((ScriptObject)value, state);
jlaskey@3 267 } else if (value instanceof ScriptObject) {
jlaskey@3 268 return JO((ScriptObject)value, state);
jlaskey@3 269 }
jlaskey@3 270 }
jlaskey@3 271
jlaskey@3 272 return UNDEFINED;
jlaskey@3 273 }
jlaskey@3 274
jlaskey@3 275 // Spec: The abstract operation JO(value) serializes an object.
jlaskey@3 276 private static String JO(final ScriptObject value, final StringifyState state) {
jlaskey@3 277 if (state.stack.containsKey(value)) {
lagergren@112 278 throw typeError("JSON.stringify.cyclic");
jlaskey@3 279 }
jlaskey@3 280
jlaskey@3 281 state.stack.put(value, value);
jlaskey@3 282 final StringBuilder stepback = new StringBuilder(state.indent.toString());
jlaskey@3 283 state.indent.append(state.gap);
jlaskey@3 284
jlaskey@3 285 final StringBuilder finalStr = new StringBuilder();
jlaskey@3 286 final List<Object> partial = new ArrayList<>();
jlaskey@3 287 final List<String> k = state.propertyList == null ? Arrays.asList(value.getOwnKeys(false)) : state.propertyList;
jlaskey@3 288
jlaskey@3 289 for (final Object p : k) {
jlaskey@3 290 final Object strP = str(p, value, state);
jlaskey@3 291
jlaskey@3 292 if (strP != UNDEFINED) {
jlaskey@3 293 final StringBuilder member = new StringBuilder();
jlaskey@3 294
sundar@82 295 member.append(JSONFunctions.quote(p.toString())).append(':');
jlaskey@3 296 if (!state.gap.isEmpty()) {
jlaskey@3 297 member.append(' ');
jlaskey@3 298 }
jlaskey@3 299
jlaskey@3 300 member.append(strP);
jlaskey@3 301 partial.add(member);
jlaskey@3 302 }
jlaskey@3 303 }
jlaskey@3 304
jlaskey@3 305 if (partial.isEmpty()) {
jlaskey@3 306 finalStr.append("{}");
jlaskey@3 307 } else {
jlaskey@3 308 if (state.gap.isEmpty()) {
jlaskey@3 309 final int size = partial.size();
jlaskey@3 310 int index = 0;
jlaskey@3 311
jlaskey@3 312 finalStr.append('{');
jlaskey@3 313
jlaskey@3 314 for (final Object str : partial) {
jlaskey@3 315 finalStr.append(str);
jlaskey@3 316 if (index < size - 1) {
jlaskey@3 317 finalStr.append(',');
jlaskey@3 318 }
jlaskey@3 319 index++;
jlaskey@3 320 }
jlaskey@3 321
jlaskey@3 322 finalStr.append('}');
jlaskey@3 323 } else {
jlaskey@3 324 final int size = partial.size();
jlaskey@3 325 int index = 0;
jlaskey@3 326
jlaskey@3 327 finalStr.append("{\n");
jlaskey@3 328 finalStr.append(state.indent);
jlaskey@3 329
jlaskey@3 330 for (final Object str : partial) {
jlaskey@3 331 finalStr.append(str);
jlaskey@3 332 if (index < size - 1) {
jlaskey@3 333 finalStr.append(",\n");
jlaskey@3 334 finalStr.append(state.indent);
jlaskey@3 335 }
jlaskey@3 336 index++;
jlaskey@3 337 }
jlaskey@3 338
jlaskey@3 339 finalStr.append('\n');
jlaskey@3 340 finalStr.append(stepback);
jlaskey@3 341 finalStr.append('}');
jlaskey@3 342 }
jlaskey@3 343 }
jlaskey@3 344
jlaskey@3 345 state.stack.remove(value);
jlaskey@3 346 state.indent = stepback;
jlaskey@3 347
jlaskey@3 348 return finalStr.toString();
jlaskey@3 349 }
jlaskey@3 350
jlaskey@3 351 // Spec: The abstract operation JA(value) serializes an array.
hannesw@223 352 private static Object JA(final ScriptObject value, final StringifyState state) {
jlaskey@3 353 if (state.stack.containsKey(value)) {
lagergren@112 354 throw typeError("JSON.stringify.cyclic");
jlaskey@3 355 }
jlaskey@3 356
jlaskey@3 357 state.stack.put(value, value);
jlaskey@3 358 final StringBuilder stepback = new StringBuilder(state.indent.toString());
jlaskey@3 359 state.indent.append(state.gap);
jlaskey@3 360 final List<Object> partial = new ArrayList<>();
jlaskey@3 361
jlaskey@3 362 final int length = JSType.toInteger(value.getLength());
jlaskey@3 363 int index = 0;
jlaskey@3 364
jlaskey@3 365 while (index < length) {
jlaskey@3 366 Object strP = str(index, value, state);
jlaskey@3 367 if (strP == UNDEFINED) {
jlaskey@3 368 strP = "null";
jlaskey@3 369 }
jlaskey@3 370 partial.add(strP);
jlaskey@3 371 index++;
jlaskey@3 372 }
jlaskey@3 373
jlaskey@3 374 final StringBuilder finalStr = new StringBuilder();
jlaskey@3 375 if (partial.isEmpty()) {
jlaskey@3 376 finalStr.append("[]");
jlaskey@3 377 } else {
jlaskey@3 378 if (state.gap.isEmpty()) {
jlaskey@3 379 final int size = partial.size();
jlaskey@3 380 index = 0;
jlaskey@3 381 finalStr.append('[');
jlaskey@3 382 for (final Object str : partial) {
jlaskey@3 383 finalStr.append(str);
jlaskey@3 384 if (index < size - 1) {
jlaskey@3 385 finalStr.append(',');
jlaskey@3 386 }
jlaskey@3 387 index++;
jlaskey@3 388 }
jlaskey@3 389
jlaskey@3 390 finalStr.append(']');
jlaskey@3 391 } else {
jlaskey@3 392 final int size = partial.size();
jlaskey@3 393 index = 0;
jlaskey@3 394 finalStr.append("[\n");
jlaskey@3 395 finalStr.append(state.indent);
jlaskey@3 396 for (final Object str : partial) {
jlaskey@3 397 finalStr.append(str);
jlaskey@3 398 if (index < size - 1) {
jlaskey@3 399 finalStr.append(",\n");
jlaskey@3 400 finalStr.append(state.indent);
jlaskey@3 401 }
jlaskey@3 402 index++;
jlaskey@3 403 }
jlaskey@3 404
jlaskey@3 405 finalStr.append('\n');
jlaskey@3 406 finalStr.append(stepback);
jlaskey@3 407 finalStr.append(']');
jlaskey@3 408 }
jlaskey@3 409 }
jlaskey@3 410
jlaskey@3 411 state.stack.remove(value);
jlaskey@3 412 state.indent = stepback;
jlaskey@3 413
jlaskey@3 414 return finalStr.toString();
jlaskey@3 415 }
jlaskey@3 416 }

mercurial