org.mozilla.javascript.NativeJSON Maven / Gradle / Ivy
The newest version!
/* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript;
import org.mozilla.javascript.json.JsonParser;
import java.util.Stack;
import java.util.Collection;
import java.util.Iterator;
import java.util.Arrays;
import java.util.List;
import java.util.LinkedList;
/**
* This class implements the JSON native object.
* See ECMA 15.12.
* @author Matthew Crumley, Raphael Speyer
*/
public final class NativeJSON extends IdScriptableObject
{
static final long serialVersionUID = -4567599697595654984L;
private static final Object JSON_TAG = "JSON";
private static final int MAX_STRINGIFY_GAP_LENGTH = 10;
static void init(Scriptable scope, boolean sealed)
{
NativeJSON obj = new NativeJSON();
obj.activatePrototypeMap(MAX_ID);
obj.setPrototype(getObjectPrototype(scope));
obj.setParentScope(scope);
if (sealed) { obj.sealObject(); }
ScriptableObject.defineProperty(scope, "JSON", obj,
ScriptableObject.DONTENUM);
}
private NativeJSON()
{
}
@Override
public String getClassName() { return "JSON"; }
@Override
protected void initPrototypeId(int id)
{
if (id <= LAST_METHOD_ID) {
String name;
int arity;
switch (id) {
case Id_toSource: arity = 0; name = "toSource"; break;
case Id_parse: arity = 2; name = "parse"; break;
case Id_stringify: arity = 3; name = "stringify"; break;
default: throw new IllegalStateException(String.valueOf(id));
}
initPrototypeMethod(JSON_TAG, id, name, arity);
} else {
throw new IllegalStateException(String.valueOf(id));
}
}
@Override
public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
Scriptable thisObj, Object[] args)
{
if (!f.hasTag(JSON_TAG)) {
return super.execIdCall(f, cx, scope, thisObj, args);
}
int methodId = f.methodId();
switch (methodId) {
case Id_toSource:
return "JSON";
case Id_parse: {
String jtext = ScriptRuntime.toString(args, 0);
Object reviver = null;
if (args.length > 1) {
reviver = args[1];
}
if (reviver instanceof Callable) {
return parse(cx, scope, jtext, (Callable) reviver);
} else {
return parse(cx, scope, jtext);
}
}
case Id_stringify: {
Object value = null, replacer = null, space = null;
switch (args.length) {
case 3: space = args[2];
/* fallthru */ case 2: replacer = args[1];
/* fallthru */ case 1: value = args[0];
/* fallthru */ case 0:
/* fallthru */ default:
}
return stringify(cx, scope, value, replacer, space);
}
default: throw new IllegalStateException(String.valueOf(methodId));
}
}
private static Object parse(Context cx, Scriptable scope, String jtext) {
try {
return new JsonParser(cx, scope).parseValue(jtext);
} catch (JsonParser.ParseException ex) {
throw ScriptRuntime.constructError("SyntaxError", ex.getMessage());
}
}
public static Object parse(Context cx, Scriptable scope, String jtext,
Callable reviver)
{
Object unfiltered = parse(cx, scope, jtext);
Scriptable root = cx.newObject(scope);
root.put("", root, unfiltered);
return walk(cx, scope, reviver, root, "");
}
private static Object walk(Context cx, Scriptable scope, Callable reviver,
Scriptable holder, Object name)
{
final Object property;
if (name instanceof Number) {
property = holder.get( ((Number) name).intValue(), holder);
} else {
property = holder.get( ((String) name), holder);
}
if (property instanceof Scriptable) {
Scriptable val = ((Scriptable) property);
if (val instanceof NativeArray) {
long len = ((NativeArray) val).getLength();
for (long i = 0; i < len; i++) {
// indices greater than MAX_INT are represented as strings
if (i > Integer.MAX_VALUE) {
String id = Long.toString(i);
Object newElement = walk(cx, scope, reviver, val, id);
if (newElement == Undefined.instance) {
val.delete(id);
} else {
val.put(id, val, newElement);
}
} else {
int idx = (int) i;
Object newElement = walk(cx, scope, reviver, val, idx);
if (newElement == Undefined.instance) {
val.delete(idx);
} else {
val.put(idx, val, newElement);
}
}
}
} else {
Object[] keys = val.getIds();
for (Object p : keys) {
Object newElement = walk(cx, scope, reviver, val, p);
if (newElement == Undefined.instance) {
if (p instanceof Number)
val.delete(((Number) p).intValue());
else
val.delete((String) p);
} else {
if (p instanceof Number)
val.put(((Number) p).intValue(), val, newElement);
else
val.put((String) p, val, newElement);
}
}
}
}
return reviver.call(cx, scope, holder, new Object[] { name, property });
}
private static String repeat(char c, int count) {
char chars[] = new char[count];
Arrays.fill(chars, c);
return new String(chars);
}
private static class StringifyState {
StringifyState(Context cx, Scriptable scope, String indent, String gap,
Callable replacer, List