package nexj.core.rpc.json;
import java.util.ArrayList;
import java.util.Collection;
import nexj.core.scripting.GenericParser;
import nexj.core.scripting.GlobalEnvironment;
import nexj.core.scripting.ParserException;
import nexj.core.util.HashTab;
import nexj.core.util.Lookup;
import nexj.core.util.TextPosition;
/**
* Parses a JSON message into a map.
*/
public class JSONParser extends GenericParser
{
// constants
/**
* Open Container {
*/
protected final static int TOKEN_OBRACE = 1;
/**
* Closing Container }
*/
protected final static int TOKEN_CBRACE = 2;
/**
* Open [
*/
protected final static int TOKEN_OBRACKET = 3;
/**
* Closing ]
*/
protected final static int TOKEN_CBRACKET = 4;
/**
* Comma ,
*/
protected final static int TOKEN_COMMA = 5;
/**
* Colon :
*/
protected final static int TOKEN_COLON = 6;
/**
* Atom (null, boolean, number, string).
*/
protected final static int TOKEN_ATOM = 7;
// constructors
/**
* Create a JSON parser with a given global environment for interning symbols.
*
* @param globalEnv The global environment, into which to store the symbols.
*/
public JSONParser(GlobalEnvironment env)
{
super(env);
}
// operations
/**
* Parses buffer for a JSON Array.
*
* @return The parsed out array as collection.
* @throws ParserException if a syntax error has been encountered.
*/
protected Collection parseArray()
{
ArrayList list = new ArrayList(4);
Object value = null;
boolean bHasValue = false;
++m_nListDepth;
if (getCurToken() != TOKEN_CBRACKET)
{
for (;;)
{
value = parseElement();
list.add(value);
bHasValue = true;
if (getCurToken() == TOKEN_CBRACKET)
{
forgetToken();
--m_nListDepth;
break;
}
else if (getCurToken() == TOKEN_COMMA)
{
if (!bHasValue)
{
fail("err.parser.json.array.unexpectedComma", null, getCurTokenPos());
}
else
{
forgetToken();
bHasValue = false;
value = null;
}
}
else
{
fail("err.parser.json.array.unexpectedToken", new Object[] { m_tokenValue }, getCurTokenPos());
}
}
}
else
{
forgetToken();
}
return list;
}
/**
* Parses buffer for a JSON value - array, object or primitive.
*
* @return The internal representation of the value.
* @throws ParserException if a syntax error has been encountered.
*/
protected Object parseValue()
{
Object value = null;
for (;;)
{
switch (getCurToken())
{
case TOKEN_EOF:
fail("err.parser.listEOF", null, getCurTokenPos());
case TOKEN_OBRACE:
value = parseElement();
return value;
case TOKEN_ATOM:
value = m_tokenValue;
forgetToken();
return value;
case TOKEN_OBRACKET:
forgetToken();
value = parseArray();
return value;
default:
fail("err.parser.json.value.unexpectedToken", new Object[] { m_tokenValue }, getCurTokenPos());
}
}
}
/**
* Parses buffer for a JSON object.
*
* @return The internal representation of the object.
* @throws ParserException if a syntax error has been encountered.
*/
protected Object parseObject()
{
String sKey = null;
Object value = null;
Lookup objMap = new HashTab(4);
++m_nListDepth;
if (getCurToken() != TOKEN_CBRACE)
{
for (;;)
{
value = parseValue();
// key
if (value instanceof String)
{
sKey = (String)m_tokenValue;
}
else
{
fail("err.parser.json.object.unexpectedKeyValue", new Object[] { value }, getCurTokenPos());
}
// value
if (getCurToken() != TOKEN_COLON)
{
fail("err.parser.unexpectedToken", new Object[] { m_tokenValue }, getCurTokenPos());
}
else
{
forgetToken();
value = parseValue();
objMap.put(sKey, value);
if (getCurToken() == TOKEN_CBRACE)
{
forgetToken();
--m_nListDepth;
break;
}
else if (getCurToken() == TOKEN_COMMA)
{
forgetToken();
sKey = null;
value = null;
}
else
{
fail("err.parser.unexpectedToken", new Object[] { m_tokenValue },getCurTokenPos());
}
}
}
}
else
{
forgetToken();
}
return objMap;
}
/**
* Parses buffer for a single JSON element - object, array or primitive.
*
* @return The internal representation of the element.
* @throws ParserException if a syntax error has been encountered.
*/
protected Object parseElement()
{
TextPosition pos = null;
Object obj;
switch (getCurToken())
{
// must start with an open brace, open array, or primitive
case TOKEN_ATOM:
obj = m_tokenValue;
forgetToken();
return obj;
case TOKEN_OBRACKET:
if (m_posMap != null)
{
pos = getCurTokenPos();
}
forgetToken();
obj = parseArray();
if (m_posMap != null && obj != null)
{
m_posMap.put(obj, pos);
}
return obj;
case TOKEN_OBRACE:
if (m_posMap != null)
{
pos = getCurTokenPos();
}
forgetToken();
obj = parseObject();
if (m_posMap != null && obj != null)
{
m_posMap.put(obj, pos);
}
return obj;
case TOKEN_CBRACE:
case TOKEN_CBRACKET:
case TOKEN_COMMA:
case TOKEN_COLON:
fail("err.parser.json.value.unexpectedToken", new Object[] { m_tokenValue },getCurTokenPos());
return null;
default:
return EOF;
}
}
/**
* @see nexj.core.scripting.GenericParser#parseToken()
*/
protected int parseToken()
{
for (;;)
{
while (Character.isWhitespace((char)getCurChar()))
{
forgetChar();
}
if (m_textPosReader != null)
{
m_nTokenLine = m_textPosReader.getTextPosition().getLine();
m_nTokenColumn = m_textPosReader.getTextPosition().getColumn();
}
m_tokenValue = Character.toString((char)m_ch);
switch (m_ch)
{
case CHAR_EOF:
return TOKEN_EOF;
case '"':
m_tokenValue = parseString();
return TOKEN_ATOM;
case ':':
forgetChar();
return TOKEN_COLON;
case '{':
forgetChar();
return TOKEN_OBRACE;
case '}':
forgetChar();
return TOKEN_CBRACE;
case '[':
forgetChar();
return TOKEN_OBRACKET;
case ']':
forgetChar();
return TOKEN_CBRACKET;
case ',':
forgetChar();
return TOKEN_COMMA;
case '-':
switch (getNextChar())
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
m_tokenValue = parseNumber(0, -1, 0, false);
return TOKEN_ATOM;
}
fail("err.parser.unexpectedToken", new Object[] { Character.toString((char)m_ch) }, getCurTextPosition());
forgetChar();
m_tokenValue = "";
return TOKEN_NONE;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
m_tokenValue = parseNumber(0, 1, 0, false);
return TOKEN_ATOM;
case '.':
switch (getNextChar())
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
m_tokenValue = parseNumber(10, 1, 2, true);
return TOKEN_ATOM;
}
fail("err.parser.unexpectedToken", new Object[] { Character.toString((char)m_ch) }, getCurTextPosition());
forgetChar();
m_tokenValue = "";
return TOKEN_NONE;
case 'n':
m_tokenValue = parseNULL();
return TOKEN_ATOM;
case 't':
case 'f':
m_tokenValue = parseBoolean();
return TOKEN_ATOM;
default:
fail("err.parser.unexpectedToken", new Object[] { m_tokenValue }, getCurTextPosition());
forgetChar();
m_tokenValue = "";
return TOKEN_NONE;
}
}
}
/**
* Parses buffer for a JSON null.
*
* @return The internal representation of null.
* @throws ParserException if a syntax error has been encountered.
*/
protected Object parseNULL()
{
m_tokenBuf.setLength(0);
m_tokenBuf.append((char)getCurChar());
for (;;)
{
markReader1();
getNextChar();
if (Character.isLetter((char)m_ch))
{
m_tokenBuf.append((char)m_ch);
continue;
}
resetReader();
break;
}
String sValue = m_tokenBuf.toString();
if (!sValue.equals("null"))
{
fail("err.parser.unexpectedToken", new Object[] { sValue }, getCurTextPosition());
}
return null;
}
/**
* Parses buffer for a JSON boolean.
*
* @return The internal representation of the boolean value.
* @throws ParserException if a syntax error has been encountered.
*/
protected Boolean parseBoolean()
{
m_tokenBuf.setLength(0);
m_tokenBuf.append((char)getCurChar());
Boolean value = null;
for (;;)
{
markReader1();
getNextChar();
if (Character.isLetter((char)m_ch))
{
m_tokenBuf.append((char)m_ch);
continue;
}
resetReader();
break;
}
String sValue = m_tokenBuf.toString();
if (sValue.equals("true"))
{
value = Boolean.TRUE;
}
else if (sValue.equals("false"))
{
value = Boolean.FALSE;
}
else
{
fail("err.parser.unexpectedToken", new Object[] { sValue }, getCurTextPosition());
}
return value;
}
}