/* Copyright 2012, UCAR/Unidata.
See the LICENSE file for more information.
*/
package ucar.nc2.util.net;
import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.util.*;
import static java.io.StreamTokenizer.*;
// Singleton class
abstract public class Json
{
//////////////////////////////////////////////////
// Constants
static final char LBRACKET = '[';
static final char RBRACKET = ']';
static final char LBRACE = '{';
static final char RBRACE = '}';
static final char COLON = ':';
static final char COMMA = ',';
static final char QUOTE = '"';
static final String TRUE = "true";
static final String FALSE = "false";
//////////////////////////////////////////////////
static public Object parse(String text)
throws IOException
{
Parser parser = new Parser();
return parser.parse(text);
}
//////////////////////////////////////////////////
static protected class Parser
{
public Parser()
{
}
// SImple recursive descent
public Object parse(String text)
throws IOException
{
StringReader rdr = new StringReader(text);
StreamTokenizer tokens = new StreamTokenizer(rdr);
tokens.eolIsSignificant(false);
tokens.quoteChar(QUOTE);
tokens.wordChars('a', 'z');
tokens.wordChars('A', 'Z');
tokens.wordChars('0', '9');
tokens.wordChars('_', '_');
tokens.wordChars('-', '-');
tokens.wordChars('$', '$');
Object result = parseR(tokens);
return result;
}
protected Object parseR(StreamTokenizer tokens)
throws IOException
{
int token = tokens.nextToken();
switch (token) {
case TT_EOF:
return null;
case TT_WORD:
return parseAtomic(tokens);
case LBRACE:
return parseMap(tokens);
case LBRACKET:
return parseArray(tokens);
case QUOTE:
return parseAtomic(tokens);
default:
throw new IOException("Unexpected token:" + (char) token);
}
}
protected Object parseAtomic(StreamTokenizer tokens)
throws IOException
{
assert (tokens.ttype == TT_WORD || tokens.ttype == QUOTE);
String word = tokens.sval;
if(tokens.ttype == QUOTE)
return word;
try {
Long l = Long.decode(word);
return l;
} catch (NumberFormatException nfe) {/*ignore*/}
;
if(word.equalsIgnoreCase(TRUE))
return Boolean.TRUE;
if(word.equalsIgnoreCase(FALSE))
return Boolean.FALSE;
return word;
}
protected Object parseArray(StreamTokenizer tokens)
throws IOException
{
assert (tokens.ttype == LBRACKET);
List<Object> array = new ArrayList<>();
loop:
for(; ; ) {
int token = tokens.nextToken();
switch (token) {
case TT_EOL:
break; // ignore
case TT_EOF:
throw new IOException("Unexpected eof");
case RBRACKET:
break loop;
default:
tokens.pushBack();
Object o = parseR(tokens);
tokens.nextToken();
if(tokens.ttype == TT_EOF) break;
else if(tokens.ttype == RBRACKET) tokens.pushBack();
else if(tokens.ttype != COMMA)
throw new IOException("Missing comma in list");
array.add(o);
}
}
return array;
}
protected Object parseMap(StreamTokenizer tokens)
throws IOException
{
assert (tokens.ttype == LBRACE);
Map<String, Object> map = new LinkedHashMap<>(); // Keep insertion order
loop:
for(; ; ) {
int token = tokens.nextToken();
switch (token) {
case TT_EOL:
break; // ignore
case TT_EOF:
throw new IOException("Unexpected eof");
case RBRACE:
break loop;
default:
tokens.pushBack();
Object name = parseR(tokens);
if(tokens.ttype == TT_EOF) break;
if(name instanceof String
|| name instanceof Long
|| name instanceof Boolean) {
/*ok*/
} else
throw new IOException("Unexpected map name type: " + name);
if(tokens.nextToken() != COLON)
throw new IOException("Expected ':'; found: " + tokens.ttype);
Object o = parseR(tokens);
tokens.nextToken();
if(tokens.ttype == TT_EOF) break;
else if(tokens.ttype == RBRACE) tokens.pushBack();
else if(tokens.ttype != COMMA)
throw new IOException("Missing comma in list");
map.put(name.toString(), o);
}
}
return map;
}
}
static public String toString(Object o) {return toString(o,"");}
static public String toString(Object o, String demark)
{
StringBuilder buf = new StringBuilder();
toStringR(o, buf, demark, 0);
return buf.toString();
}
static protected void toStringR(Object o, StringBuilder buf, String demark, int indent)
{
boolean first = true;
if(o instanceof List) {
List<Object> list = (List<Object>) o;
if(list.size()== 0) {
buf.append(LBRACKET);
buf.append(RBRACKET);
} else {
buf.append(LBRACKET);
buf.append('\n');
for(int i=0;i<list.size();i++) {
Object e = list.get(i);
buf.append(indent(indent));
toStringR(e, buf, demark, indent + 2);
if(i < list.size()-1) buf.append(",");
buf.append("\n");
}
buf.append(indent(indent));
buf.append(RBRACKET);
}
} else if(o instanceof Map) {
Map<String, Object> map = (Map<String, Object>) o;
if(map.size() == 0) {
buf.append(LBRACE);
buf.append(RBRACE);
} else {
buf.append(LBRACE);
buf.append('\n');
int i = 0;
for(Map.Entry<String, Object> e : map.entrySet()) {
buf.append(indent(indent + 2));
buf.append(QUOTE);
buf.append(e.getKey().replace("\"","\\\""));
buf.append(QUOTE);
buf.append(' ');
buf.append(COLON);
buf.append(' ');
toStringR(e.getValue(), buf, demark, indent + 2);
if(i < map.size() - 1) buf.append(",");
buf.append("\n");
i++;
}
buf.append(indent(indent));
buf.append(RBRACE);
}
} else if((o instanceof Long) || (o instanceof Boolean)) {
buf.append(demark);
buf.append(o.toString());
buf.append(demark);
} else {
buf.append(QUOTE);
buf.append(o.toString().replace("\"","\\\""));
buf.append(QUOTE);
}
}
static String blanks = " ";
static protected String indent(int n)
{
while(n > blanks.length()) {
blanks = blanks + blanks;
}
return blanks.substring(0, n);
}
}