/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/bayeux/JsonDecoder.java
/*
 * Copyright (c) 1998-2011 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.bayeux;

import java.util.ArrayList;
import java.util.HashMap;
import com.caucho.util.L10N;

class JsonDecoder {
  private static final L10N L = new L10N(JsonDecoder.class);

  private String _string;
  private int _len;
  private int _offset;

  private JsonDecoder(String string)
  {
    _string = string;
    _len = _string.length();
    _offset = 0;
  }

  public static JsonObject decode(String string)
    throws JsonDecodingException
  {
    return new JsonDecoder(string).decode();
  }
  
  private JsonObject decode()
    throws JsonDecodingException
  {
    JsonObject object = decodeObject();

    // Should now be at end of string or have only white spaces left.
    if (skipWhitespace() >= 0)
      error("expected no more input");

    return object;
  }

  /**
   * Entry point to decode a JSON value.
   *
   * @return decoded PHP value 
   */
  private JsonObject decodeObject()
    throws JsonDecodingException
  {
    for (int ch = skipWhitespace(); ch >= 0; ch = read()) {

      switch (ch) {
        case '"':
          return decodeString();

        case 't':
          if (read() == 'r' &&
              read() == 'u' &&
              read() == 'e')
            return JsonBoolean.TRUE;
          else
            error("expected 'true'");

        case 'f':
          if (read() == 'a' &&
              read() == 'l' &&
              read() == 's' &&
              read() == 'e')
            return JsonBoolean.FALSE;
          else
            error("expected 'false'");

        case 'n':
          if (read() == 'u' &&
              read() == 'l' &&
              read() == 'l')
            return JsonNull.NULL;
          else
            error("expected 'null'");

        case '[':
          return decodeArray();

        case '{':
          return decodeMap();

        default:
          if (ch == '-' || ('0' <= ch && ch <= '9')) {
            unread();

            return decodeNumber();
          }
          else
            error();
      }
    }

    error();

    return null;
  }

  /**
   * Checks to see if there is a valid number per JSON Internet Draft.
   */
  private JsonNumber decodeNumber()
    throws JsonDecodingException
  {
    StringBuilder sb = new StringBuilder();

    int ch = read();

    // (-)?
    if (ch == '-') {
      sb.append((char)ch);
      ch = read();
    }

    // (0) | ([1-9] [0-9]+)
    if (ch >= 0) {
      if (ch == '0') {
        sb.append((char)ch);
        ch = read();
      }
      else if ('1' <= ch && ch <= '9') {
        sb.append((char)ch);
        ch = read();

        while ('0' <= ch && ch <= '9') {
          sb.append((char)ch);
          ch = read();
        }
      }
      else
        error("expected 1-9");
    }

    int integerEnd = sb.length();

    // ((decimalPoint) [0-9]+)?
    if (ch == '.') {
      sb.append((char)ch);
      ch = read();

      while ('0' <= ch && ch <= '9') {
        sb.append((char)ch);
        ch = read();
      }
    }

    // ((e | E) (+ | -)? [0-9]+)
    if (ch == 'e' || ch == 'E') {
      sb.append((char)ch);
      ch = read();

      if (ch == '+' || ch == '-') {
        sb.append((char)ch);
        ch = read();
      }

      if ('0' <= ch && ch <= '9') {
        sb.append((char)ch);
        ch = read();

        while ('0' <= ch && ch <= '9') {
          sb.append((char)ch);
          ch = read();
        }
      }
      else
        error("expected 0-9 exponent");
    }

    unread();

    if (integerEnd != sb.length())
      return JsonDouble.valueOf(Double.parseDouble(sb.toString()));
    else
      return JsonLong.valueOf(Long.parseLong(sb.toString()));
  }

  /**
   * Returns an array list.
   */
  private JsonArray decodeArray()
    throws JsonDecodingException
  {
    JsonArray array = new JsonArray();

    while (true) {
      int ch = skipWhitespace();

      if (ch == ']')
        break;

      unread();

      array.add(decodeObject());

      ch = skipWhitespace();

      if (ch == ',') {
      }
      else if (ch == ']')
        break;
      else
        error("expected either ',' or ']'");
    }

    return array;
  }

  /**
   * Returns a map version of a JSON object.
   */
  private JsonMap decodeMap()
    throws JsonDecodingException
  {
    JsonMap map = new JsonMap();

    while (true) {
      int ch = skipWhitespace();

      if (ch == '}')
        break;

      unread();

      JsonObject name = decodeObject();

      if (name == null)
        error("field names must not be null");

      if (! (name instanceof JsonString))
        error("field names must be strings");

      ch = skipWhitespace();

      if (ch != ':')
        error("expected ':'");

      map.put(name.toString(), decodeObject());

      ch = skipWhitespace();

      if (ch == ',') {
      }
      else if (ch == '}')
        break;
      else
        error("expected either ',' or '}'");
    }

    return map;
  }

  /**
   * Returns a string.
   */
  private JsonString decodeString()
    throws JsonDecodingException
  {
    StringBuilder sb = new StringBuilder();

    for (int ch = read(); ch >= 0; ch = read()) {

      switch (ch) {
        // Escaped Characters
        case '\\':
          ch = read();
          if (ch < 0)
            error("invalid escape character");

          switch (ch) {
            case '"':
              sb.append('"');
              break;
            case '\\':
              sb.append('\\');
              break;
            case '/':
              sb.append('/');
              break;
            case 'b':
              sb.append('\b');
              break;
            case 'f':
              sb.append('\f');
              break;
            case 'n':
              sb.append('\n');
              break;
            case 'r':
              sb.append('\r');
              break;
            case 't':
              sb.append('\t');
              break;
            case 'u':
            case 'U':
              int hex = 0;

              for (int i = 0; i < 4; i++) {
                hex = hex << 4;
                ch = read();

                if ('0' <= ch && ch <= '9')
                  hex += ch - '0';
                else if (ch >= 'a' && ch <= 'f')
                  hex += ch - 'a' + 10;
                else if (ch >= 'A' && ch <= 'F')
                  hex += ch - 'A' + 10;
                else
                  error("invalid escaped hex character");
              }

              sb.append((char)hex);

          }
          break;

        case '"':
          return JsonString.valueOf(sb.toString());

        default:
          sb.append((char)ch);
      }
    }

    error("error decoding string");

    return null;
  }

  private void error()
    throws JsonDecodingException
  {
    error(null);
  }

  private void error(String message)
    throws JsonDecodingException
  {
    int start;
    int end;

    if (_offset < _len) {
      start = _offset - 1;
      end = _offset;
    }
    else {
      start = _len - 1;
      end = _len;
    }

    String token = _string.substring(start, end).toString();

    if (message != null)
      throw new JsonDecodingException(L.l("error parsing '{0}': {1}", 
                                          token, message));
    else
      throw new JsonDecodingException(L.l("error parsing '{0}'", token));
  }

  private void unread()
  {
    if (_offset > 0)
      _offset--;
  }

  private int peek(int index)
  {
    if (0 <= index && index < _len)
      return _string.charAt(index);
    else
      return -1;
  }

  private int read()
  {
    if (_offset < _len)
      return _string.charAt(_offset++);
    else
      return -1;
  }

  private int skipWhitespace()
  {
    int ch = read();
    for (; ch >= 0; ch = read()) {
      if (ch != ' ' &&
          ch != '\n' &&
          ch != '\r' &&
          ch != '\t')
        break;
    }

    return ch;
  }
}
