/* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; c-basic-offset: 4 -*-
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Norris Boyd
* Matthew Crumley
* Raphael Speyer
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.javascript;
import org.mozilla.javascript.json.JsonParser;
import java.util.Stack;
import java.util.Collection;
import java.util.Map;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Arrays;
import java.util.List;
import java.util.LinkedList;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
import java.lang.reflect.InvocationTargetException;
/**
* This class implements the JSON native object.
* See ECMA 15.12.
* @author Matthew Crumley, Raphael Speyer
*/
public final class JSON 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)
{
JSON obj = new JSON();
obj.activatePrototypeMap(MAX_ID);
obj.setPrototype(getObjectPrototype(scope));
obj.setParentScope(scope);
if (sealed) { obj.sealObject(); }
ScriptableObject.defineProperty(scope, "JSON", obj,
ScriptableObject.DONTENUM);
}
private JSON()
{
}
@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) {
default:
case 3: space = args[2];
case 2: replacer = args[1];
case 1: value = args[0];
case 0:
}
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) {
int len = (int) ((NativeArray) val).getLength();
for (int i = 0; i < len; i++) {
Object newElement = walk(cx, scope, reviver, val, i);
if (newElement == Undefined.instance) {
val.delete(i);
} else {
val.put(i, 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