/* -*- 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.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
*/
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) {
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<Object> propertyList,
Object space)
{
this.cx = cx;
this.scope = scope;
this.indent = indent;
this.gap = gap;
this.replacer = replacer;
this.propertyList = propertyList;
this.space = space;
}
Stack<Scriptable> stack = new Stack<Scriptable>();
String indent;
String gap;
Callable replacer;
List<Object> propertyList;
Object space;
Context cx;
Scriptable scope;
}
public static Object stringify(Context cx, Scriptable scope, Object value,
Object replacer, Object space)
{
String indent = "";
String gap = "";
List<Object> propertyList = null;
Callable replacerFunction = null;
if (replacer instanceof Callable) {
replacerFunction = (Callable) replacer;
} else if (replacer instanceof NativeArray) {
propertyList = new LinkedList<Object>();
NativeArray replacerArray = (NativeArray) replacer;
for (int i : replacerArray.getIndexIds()) {
Object v = replacerArray.get(i, replacerArray);
if (v instanceof String || v instanceof Number) {
propertyList.add(v);
} else if (v instanceof NativeString || v instanceof NativeNumber) {
propertyList.add(ScriptRuntime.toString(v));
}
}
}
if (space instanceof NativeNumber) {
space = ScriptRuntime.toNumber(space);
} else if (space instanceof NativeString) {
space = ScriptRuntime.toString(space);
}
if (space instanceof Number) {
int gapLength = (int) ScriptRuntime.toInteger(space);
gapLength = Math.min(MAX_STRINGIFY_GAP_LENGTH, gapLength);
gap = (gapLength > 0) ? repeat(' ', gapLength) : "";
space = gapLength;
} else if (space instanceof String) {
gap = (String) space;
if (gap.length() > MAX_STRINGIFY_GAP_LENGTH) {
gap = gap.substring(0, MAX_STRINGIFY_GAP_LENGTH);
}
}
StringifyState state = new StringifyState(cx, scope,
indent,
gap,
replacerFunction,
propertyList,
space);
ScriptableObject wrapper = new NativeObject();
wrapper.setParentScope(scope);
wrapper.setPrototype(ScriptableObject.getObjectPrototype(scope));
wrapper.defineProperty("", value, 0);
return str("", wrapper, state);
}
private static Object str(Object key, Scriptable holder,
StringifyState state)
{
Object value = null;
if (key instanceof String) {
value = getProperty(holder, (String) key);
} else {
value = getProperty(holder, ((Number) key).intValue());
}
if (value instanceof Scriptable) {
Object toJSON = getProperty((Scriptable) value, "toJSON");
if (toJSON instanceof Callable) {
value = callMethod(state.cx, (Scriptable) value, "toJSON",
new Object[] { key });
}
}
if (state.replacer != null) {
value = state.replacer.call(state.cx, state.scope, holder,
new Object[] { key, value });
}
if (value instanceof NativeNumber) {
value = ScriptRuntime.toNumber(value);
} else if (value instanceof NativeString) {
value = ScriptRuntime.toString(value);
} else if (value instanceof NativeBoolean) {
value = ((NativeBoolean) value).getDefaultValue(ScriptRuntime.BooleanClass);
}
if (value == null) return "null";
if (value.equals(Boolean.TRUE)) return "true";
if (value.equals(Boolean.FALSE)) return "false";
if (value instanceof String) {
return quote((String) value);
}
if (value instanceof Number) {
double d = ((Number) value).doubleValue();
if (d == d && d != Double.POSITIVE_INFINITY &&
d != Double.NEGATIVE_INFINITY)
{
return ScriptRuntime.toString(value);
} else {
return "null";
}
}
if (value instanceof Scriptable && !(value instanceof Callable)) {
if (value instanceof NativeArray) {
return ja((NativeArray) value, state);
}
return jo((Scriptable) value, state);
}
return Undefined.instance;
}
private static String join(Collection<Object> objs, String delimiter) {
if (objs == null || objs.isEmpty()) {
return "";
}
Iterator<Object> iter = objs.iterator();
if (!iter.hasNext()) return "";
StringBuilder builder = new StringBuilder(iter.next().toString());
while (iter.hasNext()) {
builder.append(delimiter).append(iter.next().toString());
}
return builder.toString();
}
private static String jo(Scriptable value, StringifyState state) {
if (state.stack.search(value) != -1) {
throw ScriptRuntime.typeError0("msg.cyclic.value");
}
state.stack.push(value);
String stepback = state.indent;
state.indent = state.indent + state.gap;
Object[] k = null;
if (state.propertyList != null) {
k = state.propertyList.toArray();
} else {
k = value.getIds();
}
List<Object> partial = new LinkedList<Object>();
for (Object p : k) {
Object strP = str(p, value, state);
if (strP != Undefined.instance) {
String member = quote(p.toString()) + ":";
if (state.gap.length() > 0) {
member = member + " ";
}
member = member + strP;
partial.add(member);
}
}
final String finalValue;
if (partial.isEmpty()) {
finalValue = "{}";
} else {
if (state.gap.length() == 0) {
finalValue = '{' + join(partial, ",") + '}';
} else {
String separator = ",\n" + state.indent;
String properties = join(partial, separator);
finalValue = "{\n" + state.indent + properties + '\n' +
stepback + '}';
}
}
state.stack.pop();
state.indent = stepback;
return finalValue;
}
private static String ja(NativeArray value, StringifyState state) {
if (state.stack.search(value) != -1) {
throw ScriptRuntime.typeError0("msg.cyclic.value");
}
state.stack.push(value);
String stepback = state.indent;
state.indent = state.indent + state.gap;
List<Object> partial = new LinkedList<Object>();
int len = (int) value.getLength();
for (int index = 0; index < len; index++) {
Object strP = str(index, value, state);
if (strP == Undefined.instance) {
partial.add("null");
} else {
partial.add(strP);
}
}
final String finalValue;
if (partial.isEmpty()) {
finalValue = "[]";
} else {
if (state.gap.length() == 0) {
finalValue = '[' + join(partial, ",") + ']';
} else {
String separator = ",\n" + state.indent;
String properties = join(partial, separator);
finalValue = "[\n" + state.indent + properties + '\n' + stepback + ']';
}
}
state.stack.pop();
state.indent = stepback;
return finalValue;
}
private static String quote(String string) {
StringBuffer product = new StringBuffer(string.length()+2); // two extra chars for " on either side
product.append('"');
int length = string.length();
for (int i = 0; i < length; i++) {
char c = string.charAt(i);
switch (c) {
case '"':
product.append("\\\"");
break;
case '\\':
product.append("\\\\");
break;
case '\b':
product.append("\\b");
break;
case '\f':
product.append("\\f");
break;
case '\n':
product.append("\\n");
break;
case '\r':
product.append("\\r");
break;
case '\t':
product.append("\\t");
break;
default:
if (c < ' ') {
product.append("\\u");
String hex = String.format("%04x", (int) c);
product.append(hex);
}
else {
product.append(c);
}
break;
}
}
product.append('"');
return product.toString();
}
// #string_id_map#
@Override
protected int findPrototypeId(String s)
{
int id;
// #generated# Last update: 2009-05-25 16:01:00 EDT
{ id = 0; String X = null;
L: switch (s.length()) {
case 5: X="parse";id=Id_parse; break L;
case 8: X="toSource";id=Id_toSource; break L;
case 9: X="stringify";id=Id_stringify; break L;
}
if (X!=null && X!=s && !X.equals(s)) id = 0;
}
// #/generated#
return id;
}
private static final int
Id_toSource = 1,
Id_parse = 2,
Id_stringify = 3,
LAST_METHOD_ID = 3,
MAX_ID = 3;
// #/string_id_map#
}