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

Mon, 08 Jul 2013 16:33:50 +0530

author
sundar
date
Mon, 08 Jul 2013 16:33:50 +0530
changeset 418
36d6b6a3fbe0
parent 414
ec84ba68ad39
child 459
0cfa27ed82fe
permissions
-rw-r--r--

8020015: shared PropertyMaps should not be used without duplication
Reviewed-by: hannesw, 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;
jlaskey@3 38 import jdk.nashorn.internal.objects.annotations.Attribute;
jlaskey@3 39 import jdk.nashorn.internal.objects.annotations.Function;
jlaskey@3 40 import jdk.nashorn.internal.objects.annotations.ScriptClass;
jlaskey@3 41 import jdk.nashorn.internal.objects.annotations.Where;
jlaskey@3 42 import jdk.nashorn.internal.runtime.ConsString;
sundar@82 43 import jdk.nashorn.internal.runtime.JSONFunctions;
jlaskey@3 44 import jdk.nashorn.internal.runtime.JSType;
hannesw@380 45 import jdk.nashorn.internal.runtime.PropertyMap;
jlaskey@3 46 import jdk.nashorn.internal.runtime.ScriptFunction;
jlaskey@3 47 import jdk.nashorn.internal.runtime.ScriptObject;
jlaskey@3 48 import jdk.nashorn.internal.runtime.arrays.ArrayLikeIterator;
jlaskey@3 49 import jdk.nashorn.internal.runtime.linker.Bootstrap;
jlaskey@3 50 import jdk.nashorn.internal.runtime.linker.InvokeByName;
jlaskey@3 51
jlaskey@3 52 /**
jlaskey@3 53 * ECMAScript 262 Edition 5, Section 15.12 The NativeJSON Object
jlaskey@3 54 *
jlaskey@3 55 */
jlaskey@3 56 @ScriptClass("JSON")
jlaskey@3 57 public final class NativeJSON extends ScriptObject {
jlaskey@3 58 private static final InvokeByName TO_JSON = new InvokeByName("toJSON", ScriptObject.class, Object.class, Object.class);
jlaskey@3 59 private static final MethodHandle REPLACER_INVOKER = Bootstrap.createDynamicInvoker("dyn:call", Object.class,
jlaskey@3 60 ScriptFunction.class, ScriptObject.class, Object.class, Object.class);
jlaskey@3 61
hannesw@380 62 // initialized by nasgen
sundar@418 63 @SuppressWarnings("unused")
hannesw@380 64 private static PropertyMap $nasgenmap$;
jlaskey@3 65
sundar@414 66 private NativeJSON() {
sundar@414 67 // don't create me!!
sundar@414 68 throw new UnsupportedOperationException();
jlaskey@3 69 }
jlaskey@3 70
jlaskey@3 71 /**
jlaskey@3 72 * ECMA 15.12.2 parse ( text [ , reviver ] )
jlaskey@3 73 *
jlaskey@3 74 * @param self self reference
jlaskey@3 75 * @param text a JSON formatted string
jlaskey@3 76 * @param reviver optional value: function that takes two parameters (key, value)
jlaskey@3 77 *
jlaskey@3 78 * @return an ECMA script value
jlaskey@3 79 */
jlaskey@3 80 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
jlaskey@3 81 public static Object parse(final Object self, final Object text, final Object reviver) {
sundar@82 82 return JSONFunctions.parse(text, reviver);
jlaskey@3 83 }
jlaskey@3 84
jlaskey@3 85 /**
jlaskey@3 86 * ECMA 15.12.3 stringify ( value [ , replacer [ , space ] ] )
jlaskey@3 87 *
jlaskey@3 88 * @param self self reference
jlaskey@3 89 * @param value ECMA script value (usually object or array)
jlaskey@3 90 * @param replacer either a function or an array of strings and numbers
jlaskey@3 91 * @param space optional parameter - allows result to have whitespace injection
jlaskey@3 92 *
jlaskey@3 93 * @return a string in JSON format
jlaskey@3 94 */
jlaskey@3 95 @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
jlaskey@3 96 public static Object stringify(final Object self, final Object value, final Object replacer, final Object space) {
jlaskey@3 97 // The stringify method takes a value and an optional replacer, and an optional
jlaskey@3 98 // space parameter, and returns a JSON text. The replacer can be a function
jlaskey@3 99 // that can replace values, or an array of strings that will select the keys.
jlaskey@3 100
jlaskey@3 101 // A default replacer method can be provided. Use of the space parameter can
jlaskey@3 102 // produce text that is more easily readable.
jlaskey@3 103
jlaskey@3 104 final StringifyState state = new StringifyState();
jlaskey@3 105
jlaskey@3 106 // If there is a replacer, it must be a function or an array.
jlaskey@3 107 if (replacer instanceof ScriptFunction) {
jlaskey@3 108 state.replacerFunction = (ScriptFunction) replacer;
jlaskey@3 109 } else if (isArray(replacer) ||
jlaskey@3 110 replacer instanceof Iterable ||
jlaskey@3 111 (replacer != null && replacer.getClass().isArray())) {
jlaskey@3 112
jlaskey@3 113 state.propertyList = new ArrayList<>();
jlaskey@3 114
jlaskey@3 115 final Iterator<Object> iter = ArrayLikeIterator.arrayLikeIterator(replacer);
jlaskey@3 116
jlaskey@3 117 while (iter.hasNext()) {
jlaskey@3 118 String item = null;
jlaskey@3 119 final Object v = iter.next();
jlaskey@3 120
jlaskey@3 121 if (v instanceof String) {
jlaskey@3 122 item = (String) v;
jlaskey@3 123 } else if (v instanceof ConsString) {
jlaskey@3 124 item = v.toString();
jlaskey@3 125 } else if (v instanceof Number ||
jlaskey@3 126 v instanceof NativeNumber ||
jlaskey@3 127 v instanceof NativeString) {
jlaskey@3 128 item = JSType.toString(v);
jlaskey@3 129 }
jlaskey@3 130
jlaskey@3 131 if (item != null) {
jlaskey@3 132 state.propertyList.add(item);
jlaskey@3 133 }
jlaskey@3 134 }
jlaskey@3 135 }
jlaskey@3 136
jlaskey@3 137 // If the space parameter is a number, make an indent
jlaskey@3 138 // string containing that many spaces.
jlaskey@3 139
jlaskey@3 140 String gap;
jlaskey@3 141
jlaskey@3 142 if (space instanceof Number || space instanceof NativeNumber) {
jlaskey@3 143 int indent;
jlaskey@3 144 if (space instanceof NativeNumber) {
jlaskey@3 145 indent = ((NativeNumber)space).intValue();
jlaskey@3 146 } else {
jlaskey@3 147 indent = ((Number)space).intValue();
jlaskey@3 148 }
jlaskey@3 149
jlaskey@3 150 final StringBuilder sb = new StringBuilder();
jlaskey@3 151 for (int i = 0; i < Math.min(10, indent); i++) {
jlaskey@3 152 sb.append(' ');
jlaskey@3 153 }
jlaskey@3 154 gap = sb.toString();
jlaskey@3 155
jlaskey@3 156 } else if (space instanceof String || space instanceof ConsString || space instanceof NativeString) {
jlaskey@3 157 final String str = (space instanceof String) ? (String)space : space.toString();
jlaskey@3 158 gap = str.substring(0, Math.min(10, str.length()));
jlaskey@3 159 } else {
jlaskey@3 160 gap = "";
jlaskey@3 161 }
jlaskey@3 162
jlaskey@3 163 state.gap = gap;
jlaskey@3 164
jlaskey@3 165 final ScriptObject wrapper = Global.newEmptyInstance();
sundar@344 166 wrapper.set("", value, false);
jlaskey@3 167
jlaskey@3 168 return str("", wrapper, state);
jlaskey@3 169 }
jlaskey@3 170
jlaskey@3 171 // -- Internals only below this point
jlaskey@3 172
jlaskey@3 173 // stringify helpers.
jlaskey@3 174
jlaskey@3 175 private static class StringifyState {
jlaskey@3 176 final Map<ScriptObject, ScriptObject> stack = new IdentityHashMap<>();
jlaskey@3 177
jlaskey@3 178 StringBuilder indent = new StringBuilder();
jlaskey@3 179 String gap = "";
jlaskey@3 180 List<String> propertyList = null;
jlaskey@3 181 ScriptFunction replacerFunction = null;
jlaskey@3 182 }
jlaskey@3 183
jlaskey@3 184 // Spec: The abstract operation Str(key, holder).
jlaskey@3 185 private static Object str(final Object key, final ScriptObject holder, final StringifyState state) {
jlaskey@3 186 Object value = holder.get(key);
jlaskey@3 187
jlaskey@3 188 try {
jlaskey@3 189 if (value instanceof ScriptObject) {
jlaskey@3 190 final ScriptObject svalue = (ScriptObject)value;
jlaskey@3 191 final Object toJSON = TO_JSON.getGetter().invokeExact(svalue);
jlaskey@3 192 if (toJSON instanceof ScriptFunction) {
jlaskey@3 193 value = TO_JSON.getInvoker().invokeExact(toJSON, svalue, key);
jlaskey@3 194 }
jlaskey@3 195 }
jlaskey@3 196
jlaskey@3 197 if (state.replacerFunction != null) {
jlaskey@3 198 value = REPLACER_INVOKER.invokeExact(state.replacerFunction, holder, key, value);
jlaskey@3 199 }
jlaskey@3 200 } catch(Error|RuntimeException t) {
jlaskey@3 201 throw t;
jlaskey@3 202 } catch(final Throwable t) {
jlaskey@3 203 throw new RuntimeException(t);
jlaskey@3 204 }
jlaskey@3 205 final boolean isObj = (value instanceof ScriptObject);
jlaskey@3 206 if (isObj) {
jlaskey@3 207 if (value instanceof NativeNumber) {
jlaskey@3 208 value = JSType.toNumber(value);
jlaskey@3 209 } else if (value instanceof NativeString) {
jlaskey@3 210 value = JSType.toString(value);
jlaskey@3 211 } else if (value instanceof NativeBoolean) {
jlaskey@3 212 value = ((NativeBoolean)value).booleanValue();
jlaskey@3 213 }
jlaskey@3 214 }
jlaskey@3 215
jlaskey@3 216 if (value == null) {
jlaskey@3 217 return "null";
jlaskey@3 218 } else if (Boolean.TRUE.equals(value)) {
jlaskey@3 219 return "true";
jlaskey@3 220 } else if (Boolean.FALSE.equals(value)) {
jlaskey@3 221 return "false";
jlaskey@3 222 }
jlaskey@3 223
jlaskey@3 224 if (value instanceof String) {
sundar@82 225 return JSONFunctions.quote((String)value);
jlaskey@3 226 } else if (value instanceof ConsString) {
sundar@82 227 return JSONFunctions.quote(value.toString());
jlaskey@3 228 }
jlaskey@3 229
jlaskey@3 230 if (value instanceof Number) {
jlaskey@3 231 return JSType.isFinite(((Number)value).doubleValue()) ? JSType.toString(value) : "null";
jlaskey@3 232 }
jlaskey@3 233
jlaskey@3 234 final JSType type = JSType.of(value);
jlaskey@3 235 if (type == JSType.OBJECT) {
jlaskey@3 236 if (isArray(value)) {
hannesw@223 237 return JA((ScriptObject)value, state);
jlaskey@3 238 } else if (value instanceof ScriptObject) {
jlaskey@3 239 return JO((ScriptObject)value, state);
jlaskey@3 240 }
jlaskey@3 241 }
jlaskey@3 242
jlaskey@3 243 return UNDEFINED;
jlaskey@3 244 }
jlaskey@3 245
jlaskey@3 246 // Spec: The abstract operation JO(value) serializes an object.
jlaskey@3 247 private static String JO(final ScriptObject value, final StringifyState state) {
jlaskey@3 248 if (state.stack.containsKey(value)) {
lagergren@112 249 throw typeError("JSON.stringify.cyclic");
jlaskey@3 250 }
jlaskey@3 251
jlaskey@3 252 state.stack.put(value, value);
jlaskey@3 253 final StringBuilder stepback = new StringBuilder(state.indent.toString());
jlaskey@3 254 state.indent.append(state.gap);
jlaskey@3 255
jlaskey@3 256 final StringBuilder finalStr = new StringBuilder();
jlaskey@3 257 final List<Object> partial = new ArrayList<>();
jlaskey@3 258 final List<String> k = state.propertyList == null ? Arrays.asList(value.getOwnKeys(false)) : state.propertyList;
jlaskey@3 259
jlaskey@3 260 for (final Object p : k) {
jlaskey@3 261 final Object strP = str(p, value, state);
jlaskey@3 262
jlaskey@3 263 if (strP != UNDEFINED) {
jlaskey@3 264 final StringBuilder member = new StringBuilder();
jlaskey@3 265
sundar@82 266 member.append(JSONFunctions.quote(p.toString())).append(':');
jlaskey@3 267 if (!state.gap.isEmpty()) {
jlaskey@3 268 member.append(' ');
jlaskey@3 269 }
jlaskey@3 270
jlaskey@3 271 member.append(strP);
jlaskey@3 272 partial.add(member);
jlaskey@3 273 }
jlaskey@3 274 }
jlaskey@3 275
jlaskey@3 276 if (partial.isEmpty()) {
jlaskey@3 277 finalStr.append("{}");
jlaskey@3 278 } else {
jlaskey@3 279 if (state.gap.isEmpty()) {
jlaskey@3 280 final int size = partial.size();
jlaskey@3 281 int index = 0;
jlaskey@3 282
jlaskey@3 283 finalStr.append('{');
jlaskey@3 284
jlaskey@3 285 for (final Object str : partial) {
jlaskey@3 286 finalStr.append(str);
jlaskey@3 287 if (index < size - 1) {
jlaskey@3 288 finalStr.append(',');
jlaskey@3 289 }
jlaskey@3 290 index++;
jlaskey@3 291 }
jlaskey@3 292
jlaskey@3 293 finalStr.append('}');
jlaskey@3 294 } else {
jlaskey@3 295 final int size = partial.size();
jlaskey@3 296 int index = 0;
jlaskey@3 297
jlaskey@3 298 finalStr.append("{\n");
jlaskey@3 299 finalStr.append(state.indent);
jlaskey@3 300
jlaskey@3 301 for (final Object str : partial) {
jlaskey@3 302 finalStr.append(str);
jlaskey@3 303 if (index < size - 1) {
jlaskey@3 304 finalStr.append(",\n");
jlaskey@3 305 finalStr.append(state.indent);
jlaskey@3 306 }
jlaskey@3 307 index++;
jlaskey@3 308 }
jlaskey@3 309
jlaskey@3 310 finalStr.append('\n');
jlaskey@3 311 finalStr.append(stepback);
jlaskey@3 312 finalStr.append('}');
jlaskey@3 313 }
jlaskey@3 314 }
jlaskey@3 315
jlaskey@3 316 state.stack.remove(value);
jlaskey@3 317 state.indent = stepback;
jlaskey@3 318
jlaskey@3 319 return finalStr.toString();
jlaskey@3 320 }
jlaskey@3 321
jlaskey@3 322 // Spec: The abstract operation JA(value) serializes an array.
hannesw@223 323 private static Object JA(final ScriptObject value, final StringifyState state) {
jlaskey@3 324 if (state.stack.containsKey(value)) {
lagergren@112 325 throw typeError("JSON.stringify.cyclic");
jlaskey@3 326 }
jlaskey@3 327
jlaskey@3 328 state.stack.put(value, value);
jlaskey@3 329 final StringBuilder stepback = new StringBuilder(state.indent.toString());
jlaskey@3 330 state.indent.append(state.gap);
jlaskey@3 331 final List<Object> partial = new ArrayList<>();
jlaskey@3 332
jlaskey@3 333 final int length = JSType.toInteger(value.getLength());
jlaskey@3 334 int index = 0;
jlaskey@3 335
jlaskey@3 336 while (index < length) {
jlaskey@3 337 Object strP = str(index, value, state);
jlaskey@3 338 if (strP == UNDEFINED) {
jlaskey@3 339 strP = "null";
jlaskey@3 340 }
jlaskey@3 341 partial.add(strP);
jlaskey@3 342 index++;
jlaskey@3 343 }
jlaskey@3 344
jlaskey@3 345 final StringBuilder finalStr = new StringBuilder();
jlaskey@3 346 if (partial.isEmpty()) {
jlaskey@3 347 finalStr.append("[]");
jlaskey@3 348 } else {
jlaskey@3 349 if (state.gap.isEmpty()) {
jlaskey@3 350 final int size = partial.size();
jlaskey@3 351 index = 0;
jlaskey@3 352 finalStr.append('[');
jlaskey@3 353 for (final Object str : partial) {
jlaskey@3 354 finalStr.append(str);
jlaskey@3 355 if (index < size - 1) {
jlaskey@3 356 finalStr.append(',');
jlaskey@3 357 }
jlaskey@3 358 index++;
jlaskey@3 359 }
jlaskey@3 360
jlaskey@3 361 finalStr.append(']');
jlaskey@3 362 } else {
jlaskey@3 363 final int size = partial.size();
jlaskey@3 364 index = 0;
jlaskey@3 365 finalStr.append("[\n");
jlaskey@3 366 finalStr.append(state.indent);
jlaskey@3 367 for (final Object str : partial) {
jlaskey@3 368 finalStr.append(str);
jlaskey@3 369 if (index < size - 1) {
jlaskey@3 370 finalStr.append(",\n");
jlaskey@3 371 finalStr.append(state.indent);
jlaskey@3 372 }
jlaskey@3 373 index++;
jlaskey@3 374 }
jlaskey@3 375
jlaskey@3 376 finalStr.append('\n');
jlaskey@3 377 finalStr.append(stepback);
jlaskey@3 378 finalStr.append(']');
jlaskey@3 379 }
jlaskey@3 380 }
jlaskey@3 381
jlaskey@3 382 state.stack.remove(value);
jlaskey@3 383 state.indent = stepback;
jlaskey@3 384
jlaskey@3 385 return finalStr.toString();
jlaskey@3 386 }
jlaskey@3 387 }

mercurial