/*
* Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Nam Nguyen
*/
package com.caucho.quercus.lib.json;
import com.caucho.quercus.annotation.Optional;
import com.caucho.quercus.env.*;
import com.caucho.quercus.module.AbstractQuercusModule;
import com.caucho.util.L10N;
import java.util.Iterator;
import java.util.Map;
public class JsonModule
extends AbstractQuercusModule
{
private static final L10N L = new L10N(JsonModule.class);
public String []getLoadedExtensions()
{
return new String[] { "json" };
}
/**
* Returns a JSON-encoded String.
*
* JSON strings can be in any Unicode format (UTF-8, UTF-16, UTF-32).
* Therefore need to pay special attention to multi-char characters.
*
* @param env
* @param val to encode into json format
* @return String JSON-encoded String
*/
public StringValue json_encode(Env env, Value val)
{
StringValue sb = env.createUnicodeBuilder();
jsonEncodeImpl(env, sb, val);
return sb;
}
private void jsonEncodeImpl(Env env, StringValue sb, Value val)
{
if (val.isString()) {
sb.append('"');
encodeString(sb, (StringValue) val);
sb.append('"');
}
else if (val == BooleanValue.TRUE)
sb.append("true");
else if (val == BooleanValue.FALSE)
sb.append("false");
else if (val instanceof NumberValue)
sb.append(val.toStringValue(env));
else if (val.isArray())
encodeArray(env, sb, (ArrayValue)val);
else if (val.isObject())
encodeObject(env, sb, (ObjectValue)val);
else if (val == null || val.isNull())
sb.append("null");
else {
env.warning(L.l("type is unsupported; encoded as null"));
}
}
private void encodeArray(Env env, StringValue sb, ArrayValue val)
{
long length = 0;
Iterator<Value> keyIter = val.getKeyIterator(env);
while (keyIter.hasNext()) {
Value key = keyIter.next();
if ((! key.isLongConvertible()) || key.toLong() != length) {
encodeAssociativeArray(env, sb, val);
return;
}
length++;
}
sb.append('[');
length = 0;
for (Value value : ((ArrayValue)val).values()) {
if (length > 0)
sb.append(',');
jsonEncodeImpl(env, sb, value);
length++;
}
sb.append(']');
}
/**
* Encodes an associative array into a JSON object.
*/
private void encodeAssociativeArray(Env env, StringValue sb, ArrayValue val)
{
sb.append('{');
int length = 0;
Iterator<Map.Entry<Value,Value>> iter = val.getIterator(env);
while (iter.hasNext()) {
Map.Entry<Value,Value> entry = iter.next();
if (length > 0)
sb.append(',');
jsonEncodeImpl(env, sb, entry.getKey().toStringValue(env));
sb.append(':');
jsonEncodeImpl(env, sb, entry.getValue());
length++;
}
sb.append('}');
}
/**
* Encodes an PHP object into a JSON object.
*/
private void encodeObject(Env env, StringValue sb, ObjectValue val)
{
sb.append('{');
int length = 0;
Iterator<Map.Entry<Value,Value>> iter = val.getIterator(env);
while (iter.hasNext()) {
Map.Entry<Value,Value> entry = iter.next();
if (length > 0)
sb.append(',');
jsonEncodeImpl(env, sb, entry.getKey().toStringValue(env));
sb.append(':');
jsonEncodeImpl(env, sb, entry.getValue());
length++;
}
sb.append('}');
}
/**
* Escapes special/control characters.
*/
private void encodeString(StringValue sb, StringValue val)
{
int len = val.length();
for (int i = 0; i < len; i++) {
char c = val.charAt(i);
switch (c) {
case '\b':
sb.append('\\');
sb.append('b');
break;
case '\f':
sb.append('\\');
sb.append('f');
break;
case '\n':
sb.append('\\');
sb.append('n');
break;
case '\r':
sb.append('\\');
sb.append('r');
break;
case '\t':
sb.append('\\');
sb.append('t');
break;
case '\\':
sb.append('\\');
sb.append('\\');
break;
case '"':
sb.append('\\');
sb.append('"');
break;
case '/':
sb.append('\\');
sb.append('/');
break;
default:
if (c <= 0x1f) {
addUnicode(sb, c);
}
else if (c < 0x80) {
sb.append(c);
}
else if ((c & 0xe0) == 0xc0 && i + 1 < len) {
int c1 = val.charAt(i + 1);
i++;
int ch = ((c & 0x1f) << 6) + (c1 & 0x3f);
addUnicode(sb, ch);
}
else if ((c & 0xf0) == 0xe0 && i + 2 < len) {
int c1 = val.charAt(i + 1);
int c2 = val.charAt(i + 2);
i += 2;
int ch = ((c & 0x0f) << 12) + ((c1 & 0x3f) << 6) + (c2 & 0x3f);
addUnicode(sb, ch);
}
else {
// technically illegal
addUnicode(sb, c);
}
break;
}
}
}
private void addUnicode(StringValue sb, int c)
{
sb.append('\\');
sb.append('u');
int d = (c >> 12) & 0xf;
if (d < 10)
sb.append((char) ('0' + d));
else
sb.append((char) ('a' + d - 10));
d = (c >> 8) & 0xf;
if (d < 10)
sb.append((char) ('0' + d));
else
sb.append((char) ('a' + d - 10));
d = (c >> 4) & 0xf;
if (d < 10)
sb.append((char) ('0' + d));
else
sb.append((char) ('a' + d - 10));
d = (c) & 0xf;
if (d < 10)
sb.append((char) ('0' + d));
else
sb.append((char) ('a' + d - 10));
}
/**
* Takes a JSON-encoded string and returns a PHP value.
*
* @param env
* @param s JSON-encoded string.
* @param assoc determines whether a generic PHP object or PHP associative
* array should be returned when decoding json objects.
* @return decoded PHP value.
*/
public Value json_decode(Env env,
StringValue s,
@Optional("false") boolean assoc)
{
if (s.length() == 0)
return new ArrayValueImpl();
return (new JsonDecoder()).jsonDecode(env, s, assoc);
}
}