package nbtool.data.json;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import nbtool.data.json.JsonParser.JsonParseException;
import nbtool.util.test.TestBase;
import nbtool.util.test.Tests;
public class Json {
public static enum JsonValueType {
OBJECT(JsonObject.class, LinkedHashMap.class),
ARRAY(JsonArray.class, ArrayList.class),
STRING(JsonString.class, String.class),
NUMBER(JsonNumber.class, Number.class),
BOOLEAN(JsonBoolean.class, Boolean.class),
NULL(Json.NULL_VALUE.getClass(), null);
public Class<?> jsonClass;
public Class<?> javaClass;
private JsonValueType(Class<?> json, Class<?> java) {
this.jsonClass = json;
this.javaClass = java;
}
}
public static interface JsonValue {
public JsonValueType type();
//JSON text of value, somewhat compact encoding.
public String serialize();
public String print();
/*
* Indenting rules:
* a JsonValue implementation is responsible for adding the proper indentation to every line
* of its plain-text rep., including the first and last.
*
* an implementation may span multiple lines but should not add a trailing newline.
* */
public String print(int indent);
//This allows you to daisy-chain subclass operations. Attempts to inline cast an object
//to an incorrect class will throw a standard java ClassCastException
public <T extends JsonValue> T cast();
public JsonArray asArray();
public JsonBoolean asBoolean();
public JsonNumber asNumber();
public JsonObject asObject();
public JsonString asString();
public JsonValue copy();
public boolean congruent(JsonValue other);
}
private static final int spacesPerIndent = 2;
protected static String prefix(int indent) {
char[] array = new char[indent * spacesPerIndent];
Arrays.fill(array, ' ');
return new String(array);
}
public static JsonValue parse(String text) throws JsonParseException {
return parse(text, 0);
}
public static JsonValue parse(String text, int from) throws JsonParseException {
JsonParser parser = new JsonParser(text, from);
return parser.parse();
}
public static JsonParser parser(String text, int start) {
return new JsonParser(text, start);
}
public static JsonValue parseAndRequireEnd(String text) throws JsonParseException {
JsonParser parser = new JsonParser(text, 0);
JsonValue ret = parser.parse();
if (parser.position() != text.length())
throw new JsonParseException("could not parse entire text", parser.position(), text);
else return ret;
}
private static final class JsonNull implements JsonValue {
@Override
public JsonValueType type() {
return JsonValueType.NULL;
}
@Override
public String serialize() {
return "null";
}
@Override
public String print() {
return this.serialize();
}
@Override
public String print(int indent) {
return Json.prefix(indent) + this.serialize();
}
/* ****************************
* this block defines the standard (only) way to implement these methods.
* Because of Java syntax, there is no great way of moving these definitions
* across classes. However, this section should be mirrored across Json classes */
@SuppressWarnings("unchecked")
@Override
public <T extends JsonValue> T cast() {
return (T) this;
}
public JsonArray asArray() {
return this.<JsonArray>cast();
}
public JsonBoolean asBoolean() {
return this.<JsonBoolean>cast();
}
public JsonNumber asNumber() {
return this.<JsonNumber>cast();
}
public JsonObject asObject() {
return this.<JsonObject>cast();
}
public JsonString asString() {
return this.<JsonString>cast();
}
@Override
public JsonValue copy() {
return NULL_VALUE;
}
@Override
public boolean congruent(JsonValue other) {
return other != null && other.type() == this.type();
}
/* end mirrored section
* ******************************/
}
public static final JsonValue NULL_VALUE = new JsonNull();
public static boolean isNull(JsonValue value) {
return (value == null || (value instanceof JsonNull));
}
public static boolean asBool(JsonValue value) {
switch (value.type()) {
case BOOLEAN:
return value.<JsonBoolean>cast().value();
case NUMBER:
return value.<JsonNumber>cast().intValue() > 0;
case STRING:
String text = value.<JsonString>cast().value();
return Boolean.valueOf(text);
default:
}
throw new ClassCastException("Cannot derive boolean from " + value.toString());
}
public static JsonObject object() {
return new JsonObject();
}
public static JsonObject object(Map<JsonString, JsonValue> map) {
JsonObject obj = new JsonObject();
obj.putAll(map);
return obj;
}
public static JsonArray array() {
return new JsonArray();
}
public static JsonArray array(List<JsonValue> vals) {
JsonArray array = new JsonArray();
array.addAll(vals);
return array;
}
public static JsonArray array(JsonValue ... vals) {
return Json.array(Arrays.asList(vals));
}
public static JsonString string() {
return new JsonString("");
}
public static JsonString string(String s) {
return new JsonString(s);
}
public static JsonNumber num(int v) {
return new JsonNumber(v);
}
public static JsonNumber num(long v) {
return new JsonNumber(v);
}
public static JsonNumber num(float v) {
return new JsonNumber(v);
}
public static JsonNumber num(double v) {
return new JsonNumber(v);
}
public static JsonBoolean bool(boolean val) {
return val ? JsonBoolean.TRUE : JsonBoolean.FALSE;
}
public static JsonNull nullv() {
return (JsonNull) NULL_VALUE;
}
public static void _NBL_ADD_TESTS_() {
Tests.add("json", new TestBase("encode") {
@Override
public boolean testBody() throws Exception {
JsonArray witho = new JsonArray();
JsonArray with = new JsonArray();
witho.add(5.0);
witho.add(100);
witho.add("string");
with.add(new JsonNumber(5.0));
with.add(new JsonNumber(100));
with.add(new JsonString("string"));
requireEqual(witho.serialize(), with.serialize());
JsonArray array1 = new JsonArray();
array1.add(new JsonString("abc"));
array1.add(new JsonNumber(123));
array1.add(new JsonNumber(222.123));
array1.add(JsonBoolean.TRUE);
requireEqual(array1.serialize(), "[\"abc\",123,222.123,true]");
JsonObject obj1 = new JsonObject();
obj1.put("array1", array1);
requireEqual("{\"array1\":[\"abc\",123,222.123,true]}", obj1.serialize());
obj1.remove("array1");
assert(obj1.size() == 0);
array1.add(obj1);
requireEqual("[\"abc\",123,222.123,true,{}]", array1.serialize());
JsonObject map = new JsonObject();
map.put("k31", Json.string("v3"));
map.put("k32", Json.num(123.45));
map.put("k33", Json.bool(false));
map.put("k34", Json.NULL_VALUE);
List<JsonValue> l = new LinkedList<>();
l.add(Json.string("vvv"));
l.add(Json.num(1.23456789123456789));
l.add(Json.bool(true));
l.add(Json.NULL_VALUE);
map.put("list", Json.array(l));
String correct = "{\"k31\":\"v3\",\"k32\":123.45,\"k33\":false,\"k34\":null,\"list\":[\"vvv\",1.234567891234568,true,null]}";
String first = map.serialize();
requireEqual(correct, first);
requireEqual(Json.parse(correct).serialize(), first);
return true;
}
});
Tests.add("json", new TestBase("decode"){
@Override
public boolean testBody() throws Exception {
//Good strings
try {
String s="[0,{\"1\":{\"2\":{\"3\":{\"4\":[5,{\"6\":7}]}}}}]";
JsonArray obj = Json.parse(s).<JsonArray>cast();
requireEqual("{\"1\":{\"2\":{\"3\":{\"4\":[5,{\"6\":7}]}}}}", obj.get(1).serialize());
JsonObject obj2 = obj.get(1).<JsonObject>cast();
//System.out.println(obj2.print());
requireEqual("{\"2\":{\"3\":{\"4\":[5,{\"6\":7}]}}}", obj2.get("1").serialize());
s="{}";
JsonValue obj3 = Json.parse(s);
requireEqual("{}",obj3.serialize());
s="[\"hello\\bworld\\\"abc\\tdef\\\\ghi\\rjkl\\n123\\\"\"]";
//Logger.println(s);
JsonValue obj4 = Json.parse(s);
//Logger.println(obj4.serialize());
requireEqual("\"hello\\bworld\\\"abc\\tdef\\\\ghi\\rjkl\\n123\\\"\"",
obj4.<JsonArray>cast().get(0).serialize());
//Logger.println("after");
s= "45.0";
JsonValue obj5 = Json.parse(s);
JsonNumber num = obj5.<JsonNumber>cast();
assert(num.doubleValue() == 45.0);
s="0x43";
JsonValue obj6 = Json.parse(s);
JsonNumber num2 = obj6.<JsonNumber>cast();
assert(num2.intValue() == Integer.decode(s));
s="{\"first\": 123, \"second\": [4, 5, 6], \"third\": 789}";
JsonObject obj7 = Json.parse(s).<JsonObject>cast();
assert(obj7.get("first").<JsonNumber>cast().intValue() == 123
);
assert(obj7.get("second").<JsonArray>cast().size() == 3);
assert(obj7.get("third").<JsonNumber>cast().intValue() == 789);
} catch (JsonParseException e) {
e.printStackTrace();
return false;
}
//requireInvalid("name"); //valid string, test of requireInvalid()
requireInvalid("[5,]");
requireInvalid("[5,,2]");
requireInvalid("{\"name\":");
requireInvalid("{\"name\":}");
requireInvalid("{\"name");
requireInvalid("[[null, 123.45, \"a\\b c\"}, true]");
return true;
}
private void requireInvalid(String s) throws Exception {
boolean thrown = false;
try {
Json.parse(s);
} catch (JsonParseException jpe) {
thrown = true;
} finally {
if (!thrown) {
failed(String.format("parser did not throw on invalid string '%s' ", s));
}
}
}
} , new TestBase("congruence") {
@Override
public boolean testBody() throws Exception {
JsonNumber num = Json.num(100);
JsonNumber num1 = Json.num(100);
JsonNumber num2 = Json.num(200);
JsonNumber num3 = Json.num(300);
assert(num.congruent(num1));
assert(!num.congruent(num3));
JsonBoolean b1 = Json.bool(true);
JsonBoolean b2 = Json.bool(false);
JsonBoolean b3 = Json.bool(true);
assert(b1.congruent(b3));
assert(!b2.congruent(b3));
JsonArray a1 = Json.array(num, b1, b2);
JsonArray a2 = Json.array(Json.num(100), b1, b2);
JsonArray a3 = Json.array(num, b1, b2);
JsonArray a4 = Json.array();
JsonArray a5 = Json.array(num, b2, b1);
assert(a1.congruent(a2));
assert(a1.congruent(a3));
assert(!a1.congruent(a4));
assert(!a1.congruent(a5));
JsonString s1 = Json.string("thing");
JsonString s2 = Json.string("thinG");
JsonString s3 = Json.string("thing");
assert(s1.congruent(s1));
assert(s1.congruent(s3));
assert(!s1.congruent(s2));
JsonObject o1 = Json.object();
o1.put("a", a1);
JsonObject o2 = Json.object();
o2.put("a", a3);
assert(o1.congruent(o2));
JsonObject o3 = Json.object();
assert(!o1.congruent(o3));
return true;
}
}
);
}
}