aboutsummaryrefslogblamecommitdiffstats
path: root/infrastructure/net.appjet.oui/FastJSON.scala
blob: 60cfc48af0640ed861954e176afadf7e05587a1c (plain) (tree)










































































































































































                                                                                        
/**
 * Copyright 2009 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.appjet.oui;

import org.mozilla.javascript.{Context,Scriptable,ScriptableObject};
import org.json.{JSONStringer,JSONObject,JSONArray};

object FastJSON {
  def stringify(rhinoObj: Scriptable): String = {
    return FastJSONStringify.stringify(rhinoObj);
  }
  def parse(exctx: ExecutionContext, source: String): Scriptable = {
    return (new FastJSONParser(exctx)).parse(source);
  }
}

//----------------------------------------------------------------
// FastJSONStringify
//----------------------------------------------------------------
object FastJSONStringify {

  def stringify(rhinoObj: Scriptable): String = {
    val stringer = new JSONStringer();
    stringerizeScriptable(stringer, rhinoObj);
    return stringer.toString();
  }

  private def stringerize(s: JSONStringer, v: Object) {
    if (v == Context.getUndefinedValue) {
      return;
    }
    v match {
      case (o:Scriptable) => stringerizeScriptable(s, o);
      case (o:Number) => {
        val d = o.doubleValue;
        if (d.toLong.toDouble == d) {
          s.value(d.toLong);
        }
        else {
          s.value(o);
        }
      }
      case o => s.value(o);
    }
  }

  private def stringerizeScriptable(stringer: JSONStringer, rhinoObj: Scriptable) {
    if (rhinoObj.getClassName() == "Array") {
      stringerizeArray(stringer, rhinoObj);
    } else {
      stringerizeObj(stringer, rhinoObj);
    }
  }

  private def stringerizeObj(stringer: JSONStringer, rhinoObj: Scriptable) {
    stringer.`object`();

    for (id <- rhinoObj.getIds()) {
      val k = id.toString();
      var v:Object = null;
      id match {
        case (s:String) => { v = rhinoObj.get(s, rhinoObj); }
        case (n:Number) => { v = rhinoObj.get(n.intValue, rhinoObj); }
        case _ => {}
      }
      
      if (v != null && v != Scriptable.NOT_FOUND && v != Context.getUndefinedValue) {
        stringer.key(k);
        stringerize(stringer, v);
      }
    }

    stringer.endObject();
  }

  private def stringerizeArray(stringer: JSONStringer, rhinoArray: Scriptable) {
    stringer.`array`();

    val ids:Array[Object] = rhinoArray.getIds();
    var x = 0;
    for (i <- 0 until ids.length) {
      // we ignore string keys on js arrays.  crockford's "offical"
      // json library does this as well.
      if (ids(i).isInstanceOf[Number]) {
        val id:Int = ids(i).asInstanceOf[Number].intValue;
        while (x < id) {
          stringer.value(null);
          x += 1;
        }
        val v:Object = rhinoArray.get(id, rhinoArray);
        stringerize(stringer, v);
        x += 1;
      }
    }

    stringer.endArray();
  }
}

//----------------------------------------------------------------
// FastJSONParse
//----------------------------------------------------------------
class FastJSONParser(val ctx:ExecutionContext) {

  def parse(source: String): Scriptable = {
    if (source(0) == '[') {
      jsonToRhino(new JSONArray(source)).asInstanceOf[Scriptable];
    } else {
      jsonToRhino(new JSONObject(source)).asInstanceOf[Scriptable];
    }
  }

  private def newObj(): Scriptable = {
    Context.getCurrentContext().newObject(ctx.runner.globalScope);
  }

  private def newArray(): Scriptable = {
    Context.getCurrentContext().newArray(ctx.runner.globalScope, 0);
  }
  
  private def jsonToRhino(json: Object): Object = {
    json match {
      case (o:JSONArray) => jsonArrayToRhino(o);
      case (o:JSONObject) => jsonObjectToRhino(o);
      case o if (o == JSONObject.NULL) => null;
      case o => o;
    }
  }

  private def jsonArrayToRhino(json: JSONArray): Scriptable = {
    val o:Scriptable = newArray();
    for (i <- 0 until json.length()) {
      o.put(i, o, jsonToRhino(json.get(i)));
    }
    return o;
  }

  private def jsonObjectToRhino(json: JSONObject): Scriptable = {
    val o:Scriptable = newObj();
    val names:Array[String] = JSONObject.getNames(json);
    if (names != null) {
      for (n <- names) {
        val i = try { Some(n.toInt); } catch { case (e:NumberFormatException) => None };
        if (i.isDefined) {
          o.put(i.get, o, jsonToRhino(json.get(n)));
        }
        else {
          o.put(n, o, jsonToRhino(json.get(n)));
        }
      }
    }
    return o;
  }

}