Package com.skaringa.json.parser

Source Code of com.skaringa.json.parser.JsonParser

package com.skaringa.json.parser;

import java.io.IOException;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Stack;

import org.xml.sax.Attributes;

import com.skaringa.javaxml.DeserializerException;
import com.skaringa.javaxml.PropertyKeys;
import com.skaringa.javaxml.handler.AttrImpl;
import com.skaringa.javaxml.handler.sax.ObjectDeserializerHolder;
import com.skaringa.javaxml.impl.PropertyHelper;
import com.skaringa.javaxml.serializers.ComponentSerializer;
import com.skaringa.javaxml.serializers.SerializerRegistry;
import com.skaringa.util.Log;

/**
* Parse JSON into Java objects.
*
*/
public class JsonParser {

  private static final char QUOTE = '"';
  private static final char END_ARRAY = ']';
  private static final char BEGIN_ARRAY = '[';
  private static final char END_OBJECT = '}';
  private static final char BEGIN_OBJECT = '{';
  private static final char COLON = ':';
  private static final char COMMA = ',';

  private StreamTokenizer m_tokenizer;
  private Stack _objHolderStack = new Stack();
  private Class _rootType;
  private String _name;
  private ClassLoader _classLoader;
  private Map _propertyMap = new HashMap();
  private static final Attributes emptyAttrs = new AttrImpl();
  private static final String endOfValue = new String(new char[] {END_ARRAY, END_OBJECT, COMMA});

  /**
   * Create a new parser.
   *
   * @param reader
   *          The reader that produces the JSON.
   * @param rootType
   *          The type of the root object. If null, then LinkedList or HashMap
   *          is used.
   * @param classLoader
   *          The class loader used to instantiate the Java objects.
   */
  public JsonParser(Reader reader, Class rootType, ClassLoader classLoader) {
    m_tokenizer = new StreamTokenizer(reader);

    setSyntax();

    _rootType = rootType;
    _classLoader = classLoader;
  }

  /**
   * Create a new parser.
   *
   * @param reader
   *          The reader that produces the JSON.
   * @param rootType
   *          The type of the root object. If null, then LinkedList or HashMap
   *          is used.
   * @param propertyMap
   *          The properties to control the parsing.
   * @param classLoader
   *          The class loader used to instantiate the Java objects.
   */
  public JsonParser(Reader reader, Class rootType, Map propertyMap,
      ClassLoader classLoader) {
    m_tokenizer = new StreamTokenizer(reader);

    setSyntax();

    _rootType = rootType;
    _propertyMap = propertyMap;
    _classLoader = classLoader;
  }

  /**
   * Parse the JSON and instantiate the Java object tree.
   *
   * @throws IOException
   *           If the reader fails.
   * @throws DeserializerException
   *           If the desrialization failes (e.g. because of invalid JSON).
   */
  public void process() throws IOException, DeserializerException {
    int type = m_tokenizer.nextToken();
    while (type != StreamTokenizer.TT_EOF) {
      processToken(type);
      type = m_tokenizer.nextToken();
    }
  }

  /**
   * Set the type of the root object to be deserialized.
   *
   * @param type
   *          The class of the root object.
   */
  public void setRootType(Class type) {
    _rootType = type;
  }

  /**
   * Get the deserialized object tree. The type of object is the same as
   * rootType. If rootType is null, then the type is Collection or Map depending
   * on the JSON parsed.
   *
   * @return The new Object.
   */
  public Object getObject() {
    return ((ObjectDeserializerHolder) _objHolderStack.peek()).getObj();
  }

  /**
   * Set the class loader used to load classes during deserialization.
   *
   * @param loader
   *          The class loader.
   */
  public void setClassLoader(ClassLoader loader) {
    _classLoader = loader;
  }

  /**
   * Get the class loader used to load classes during deserialization.
   *
   * @return The class loader.
   */
  public ClassLoader getClassLoader() {
    return _classLoader;
  }

  private void setSyntax() {
    m_tokenizer.resetSyntax();
    m_tokenizer.eolIsSignificant(false);
    m_tokenizer.slashSlashComments(false);
    m_tokenizer.slashStarComments(false);
    m_tokenizer.ordinaryChar(QUOTE);
    m_tokenizer.ordinaryChar(COLON);
    m_tokenizer.ordinaryChar(BEGIN_OBJECT);
    m_tokenizer.ordinaryChar(END_OBJECT);
    m_tokenizer.ordinaryChar(BEGIN_ARRAY);
    m_tokenizer.ordinaryChar(END_ARRAY);
    m_tokenizer.wordChars('a', 'z');
    m_tokenizer.wordChars('A', 'Z');
    m_tokenizer.wordChars('0', '9');
    m_tokenizer.wordChars('-', '.');
    m_tokenizer.wordChars('+', '+');
    m_tokenizer.whitespaceChars(' ', ' ');
    m_tokenizer.whitespaceChars('\t', '\t');
    m_tokenizer.whitespaceChars('\r', '\r');
    m_tokenizer.whitespaceChars('\n', '\n');
  }

  private void processToken(int type) throws IOException, DeserializerException {
    if (type == BEGIN_OBJECT) {
      beginObject();
    } else if (type == END_OBJECT) {
      endObject();
    } else if (type == BEGIN_ARRAY) {
      beginArray();
    } else if (type == END_ARRAY) {
      endArray();
    } else if (type == QUOTE) {
      String sval = readQuotedString();
      if (test(COLON)) {
        // sval is a key
        name(sval);
      } else {
        // sval is a string value
        string(sval);
        expectEndOfValue();
      }
    } else if (type == StreamTokenizer.TT_WORD) {
      // sval is a numeric or boolean or null value
      numberOrBooleanOrNull(m_tokenizer.sval);
      expectEndOfValue();
    } else if (type == COMMA) {
      // eat
    } else {
      throw new DeserializerException("Unexpected token: "
          + (type == StreamTokenizer.TT_WORD ? m_tokenizer.sval : String
              .valueOf((char) type)));
    }
  }


  /**
   * Read a quoted string. Unfortunately StreamTokenizer doesn't handle unicode
   * sequences, like \u0000, correctly.
   *
   * @return The string without leading and trailing quotes.
   * @throws IOException
   */
  private String readQuotedString() throws IOException {
    m_tokenizer.resetSyntax();
    StringBuffer sval = new StringBuffer();
    int type = m_tokenizer.nextToken();
    while (type != QUOTE) {
      if (type == StreamTokenizer.TT_EOF) {
        throw new IOException("EOF in quoted string");
      } else if (type == StreamTokenizer.TT_WORD) {
        sval.append(m_tokenizer.sval);
      } else if (type == '\\') {
        type = m_tokenizer.nextToken();
        switch (type) {
        case '"':
          sval.append('"');
          break;
        case '\\':
          sval.append('\\');
          break;
        case '/':
          sval.append('/');
          break;
        case 'b':
          sval.append('\b');
          break;
        case 'f':
          sval.append('\f');
          break;
        case 'n':
          sval.append('\n');
          break;
        case 'r':
          sval.append('\r');
          break;
        case 't':
          sval.append('\t');
          break;
        case 'u':
          sval.append(readUnicodeSeq());
          break;
        default:
          sval.append((char) type);
          break;
        }
      } else {
        sval.append((char) type);
      }

      type = m_tokenizer.nextToken();
    }

    setSyntax();
    return sval.toString();
  }

  private char readUnicodeSeq() throws IOException {
    char[] buf = new char[4];
    for (int i = 0; i < 4; ++i) {
      int tok = m_tokenizer.nextToken();
      if ((tok >= '0' && tok <= '9') || (tok >= 'a' && tok <= 'f')
          || (tok >= 'A' && tok <= 'F')) {
        buf[i] = (char) tok;
      } else {
        throw new IOException("invalid unicode sequence");
      }
    }
    return (char) Integer.parseInt(new String(buf), 16);
  }

  private void name(String sval) throws DeserializerException {
    Log.debug("name", sval);
   
    ObjectDeserializerHolder objHolder = (ObjectDeserializerHolder) _objHolderStack.peek();
    if (END_OBJECT != objHolder.getJsonStructureTag()) {
      throw new DeserializerException("wrong containment of ':' inside array.");
    }
   
    _name = sval;
  }

  private void beginObject() throws IOException, DeserializerException {
    Log.debug("beginObject");

    begin(HashMap.class, END_OBJECT);
  }

  private void endObject() throws DeserializerException {
    Log.debug("endObject");

    end(null, END_OBJECT);
  }

  private void beginArray() throws DeserializerException {
    Log.debug("beginArray");

    begin(LinkedList.class, END_ARRAY);
  }

  private void endArray() throws DeserializerException {
    Log.debug("endArray");

    end(null, END_ARRAY);
  }

  private void begin(Class fallbackType, char jsonStructureTag)
      throws DeserializerException {
    if (_rootType == null) {
      // root type not set - deserializing into fallbackType
      _rootType = fallbackType;
    }

    Object parent = getParent();

    ComponentSerializer ser;
    if (parent == null) {
      ser = SerializerRegistry.getInstance().getSerializer(_rootType);
    } else {
      try {
        ser = SerializerRegistry.getInstance().findDeserializer(parent, _name);
      } catch (DeserializerException e) {
        // use fallbackType
        ser = SerializerRegistry.getInstance().getSerializer(fallbackType);
      }
    }

    Object obj = ser.startDeserialize(_name, emptyAttrs, parent,
        _objHolderStack, _classLoader);

    _objHolderStack.push(new ObjectDeserializerHolder(obj, ser, _name,
        jsonStructureTag));
   
    _name = null;
  }

  private Object getParent() {
    // get parent
    Object parent = null;
    try {
      parent = getObject();
    } catch (java.util.EmptyStackException e) {
      // no parent object available
    }
    return parent;
  }

  private void end(String val, char jsonStructureTag)
      throws DeserializerException {
    ObjectDeserializerHolder objHolder = (ObjectDeserializerHolder) _objHolderStack
        .pop();
    if (jsonStructureTag != objHolder.getJsonStructureTag()) {
      throw new DeserializerException("Wrong sequence: Expected '"
          + jsonStructureTag + "' but was '" + objHolder.getJsonStructureTag()
          + "'");
    }

    Object obj = objHolder.getSer().endDeserialize(objHolder.getObj(), val);
    objHolder.setObj(obj);

    try {
      ObjectDeserializerHolder parentHolder = (ObjectDeserializerHolder) _objHolderStack
          .peek();
      parentHolder.getSer().setMember(parentHolder.getObj(),
          objHolder.getName(), obj);
    } catch (java.util.EmptyStackException e) {
      // leave the top level object at the stack
      _objHolderStack.push(objHolder);
    } catch (NoSuchFieldException e) {
      if (!PropertyHelper.parseBoolean(_propertyMap,
          PropertyKeys.SKIP_UNKNOWN_FIELDS)) {
        throw new DeserializerException("no such field: " + e.getMessage());
      }
    }
    _name = null;
  }

  private void string(String sval) throws DeserializerException {
    Log.debug("string", sval);

    begin(String.class, QUOTE);
    end(sval, QUOTE);
  }

  private void numberOrBooleanOrNull(String sval) throws DeserializerException {
    Log.debug("value", sval);

    Object parent = getParent();

    ComponentSerializer ser;

    boolean isNull = false;
    if ("null".equals(sval)) {
      // null
      ser = SerializerRegistry.getInstance().getSerializer(Object.class);
      isNull = true;
    } else if ("true".equals(sval)) {
      // boolean true
      ser = SerializerRegistry.getInstance().getSerializer(Boolean.class);
    } else if ("false".equals(sval)) {
      // boolean false
      ser = SerializerRegistry.getInstance().getSerializer(Boolean.class);
    } else {
      // number
      try {
        // find deserializer from parent's member
        ser = SerializerRegistry.getInstance().findDeserializer(parent, _name);
      } catch (DeserializerException e) {
        // if not found guess numeric type
        ser = SerializerRegistry.getInstance().guessDeserializerForNumber(sval);
      }
    }

    Object obj = null;
    if (!isNull) {
      obj = ser.startDeserialize(_name, emptyAttrs, parent, _objHolderStack,
          _classLoader);
    }

    _objHolderStack.push(new ObjectDeserializerHolder(obj, ser, _name, '\0'));

    end(sval, '\0');
  }

  private boolean test(int type) throws IOException {
    int isType = m_tokenizer.nextToken();
    if (isType != type) {
      m_tokenizer.pushBack();
    }
    return (isType == type);
  }

  private void expectEndOfValue() throws DeserializerException,
      IOException {
    int token = m_tokenizer.nextToken();
    if (endOfValue.indexOf((char) token) < 0) {
      throw new DeserializerException("Parsing error: Expected one of '" + endOfValue
          + "' but got '" + (char) token + "'");
    }
    m_tokenizer.pushBack();
  }

}
TOP

Related Classes of com.skaringa.json.parser.JsonParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.