/* @test
* @summary Test JSON parser for a random json node tree
* @modules java.management.rest/com.oracle.jmx.remote.rest.json
* java.management.rest/com.oracle.jmx.remote.rest.json.parser
* @build JsonParserTest
* @run main JsonParserTest
*/
import com.oracle.jmx.remote.rest.json.parser.JSONParser;
import com.oracle.jmx.remote.rest.json.parser.ParseException;
import java.util.*;
/**
* Below class tests JSON parser for a randomly generated JSON string.
* The Json string is generated by converting a randomly generated tree into a string
* Each node in the tree is either a json Object, array or a primitive.
* Primitive node generates string, number, boolean or null as a string.
* Json Number is generated according to the syntax graph as in json.org
* Json string is generated along with all the control characters, and by escaping
* only backslash and double quote characters.
*/
public class JsonParserTest {
static final Random RANDOM = new Random(System.currentTimeMillis());
static int maxChildrenPerNode;
public static void main(String[] args) throws ParseException {
for(int i=0; i<100; i++) {
maxChildrenPerNode = RANDOM.nextInt(90) + 10;
int totalNodes = RANDOM.nextInt(90000) + 10000;
boolean isArray = RANDOM.nextBoolean(); // Generate either a Json Array or a Json Object
JsonNode node;
if (isArray) {
node = JsonNodeGenerator.ArrayGenerator.generate(totalNodes);
} else {
node = JsonNodeGenerator.ObjectGenerator.generate(totalNodes);
}
String str = node.toJsonString();
JSONParser parser = new JSONParser(str);
com.oracle.jmx.remote.rest.json.JSONElement parse = parser.parse();
parse.toJsonString();
System.out.println("Finished iteration : " + i + ", Node count :" + totalNodes);
}
}
}
interface JsonNode {
class ObjectNode extends LinkedHashMap<String, JsonNode> implements JsonNode {
@Override
public String toJsonString() {
if (isEmpty()) {
return null;
}
StringBuilder sbuild = new StringBuilder();
sbuild.append("{");
keySet().forEach((elem) -> sbuild.append(elem).append(": ").
append((get(elem) != null) ? get(elem).toJsonString() : "null").append(","));
sbuild.deleteCharAt(sbuild.lastIndexOf(","));
sbuild.append("}");
return sbuild.toString();
}
}
class ArrayNode extends ArrayList<JsonNode> implements JsonNode {
@Override
public String toJsonString() {
if (isEmpty()) {
return null;
}
StringBuilder sbuild = new StringBuilder();
sbuild.append("[");
for (JsonNode val : this) {
if (val != null) {
sbuild.append(val.toJsonString()).append(", ");
} else {
sbuild.append("null").append(", ");
}
}
sbuild.deleteCharAt(sbuild.lastIndexOf(","));
sbuild.append("]");
return sbuild.toString();
}
}
class PrimitiveNode implements JsonNode {
private final String s;
PrimitiveNode(String s) {
this.s = s;
}
@Override
public String toJsonString() {
return s;
}
}
String toJsonString();
}
interface JsonNodeGenerator {
class NumberGenerator {
// Node that returns the assigned label
private static class Node {
final private String label;
Node(String label) {
children = new LinkedList<>();
this.label = label;
}
Node() {
this("");
}
void add(Node node) {
if (!children.contains(node)) {
children.add(node);
}
}
String getLabel() {
return label;
}
List<Node> children;
}
// Node that generates a random digit from 1-9
private static class Digit19 extends Node {
Digit19() {
super();
}
@Override
String getLabel() {
return "" + (JsonParserTest.RANDOM.nextInt(9) + 1);
}
}
// Node that generates a random digit from 0-9
private static class Digits extends Node {
Digits() {
super();
}
@Override
String getLabel() {
return "" + (JsonParserTest.RANDOM.nextInt(10));
}
}
private final static Node root;
// Setup a graph for the grammar productions for JSON number as outlined in json.org
// The graph below mimics the syntax diagram for JSON number.
// Node "R" is the start node and "T" is the terminal node
static {
// Create all the nodes
root = new Node("R");
Node minus1 = new Node("-");
Node zero = new Node("0");
Node digit19 = new Digit19();
Node digits1 = new Digits();
Node dot = new Node(".");
Node digits2 = new Digits();
Node e = new Node("e");
Node E = new Node("E");
Node plus = new Node("+");
Node minus2 = new Node("-");
Node digits3 = new Digits();
Node terminal = new Node("T");
//set up graph
root.add(zero);
root.add(minus1);
root.add(digit19);
minus1.add(zero);
minus1.add(digit19);
zero.add(dot);
zero.add(terminal);
digit19.add(dot);
digit19.add(digits1);
digit19.add(terminal);
digits1.add(dot);
digits1.add(digits1);
digits1.add(terminal);
dot.add(digits2);
digits2.add(digits2);
digits2.add(e);
digits2.add(E);
digits2.add(terminal);
e.add(plus);
e.add(minus2);
e.add(digits3);
E.add(plus);
E.add(minus2);
E.add(digits3);
plus.add(digits3);
minus2.add(digits3);
digits3.add(digits3);
digits3.add(terminal);
}
static String generate() {
// Get a random path from start to finish
StringBuilder sbuf = new StringBuilder();
Node parent = root;
Node child = parent.children.get(JsonParserTest.RANDOM.nextInt(parent.children.size()));
while (!child.getLabel().equals("T")) {
sbuf.append(child.getLabel());
parent = child;
child = parent.children.get(JsonParserTest.RANDOM.nextInt(parent.children.size()));
}
return sbuf.toString();
}
}
class StringGenerator {
private static final int minStringLength = 0;
private static final int maxStringLength = 50;
private static final String controlChars = "\b" + "\f" + "\n" + "\r" + "\t" + "\\b";
// private static final String escapedControls = "\\b" + "\\f" + "\\n" + "\\r" + "\\t" + "\\b";
private static final String specials = "\\" + "\"" + controlChars;// + escapedControls; // TODO: "\\uxxxx"
// private static final String alphanums = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
static String generate() {
char ch;
StringBuilder sbuf = new StringBuilder();
int len = minStringLength + JsonParserTest.RANDOM.nextInt(maxStringLength - minStringLength + 1);
sbuf.append("\"");
for (int i = 0; i < len; i++) {
if (JsonParserTest.RANDOM.nextInt(10) == 1) { // 1/10 chances of a control character
ch = specials.charAt(JsonParserTest.RANDOM.nextInt(specials.length()));
} else {
// ch = alphanums.charAt(JsonParserTest.RANDOM.nextInt(alphanums.length()));
ch = (char) JsonParserTest.RANDOM.nextInt(Character.MAX_VALUE + 1);
}
switch (ch) {
case '\"':
case '\\':
sbuf.append('\\');
}
sbuf.append(ch);
}
sbuf.append("\"");
return sbuf.toString();
}
}
class ArrayGenerator {
static JsonNode.ArrayNode generate(int size) {
JsonNode.ArrayNode array = new JsonNode.ArrayNode();
if (size <= JsonParserTest.maxChildrenPerNode) {
for (int i = 0; i < size; i++) {
array.add(PrimtiveGenerator.generate());
}
} else if (size >= JsonParserTest.maxChildrenPerNode) {
int newSize = size;
do {
int childSize = JsonParserTest.RANDOM.nextInt(newSize);
if (JsonParserTest.RANDOM.nextBoolean()) {
array.add(ArrayGenerator.generate(childSize));
} else {
array.add(ObjectGenerator.generate(childSize));
}
newSize = newSize - childSize;
} while (newSize > JsonParserTest.maxChildrenPerNode);
if (JsonParserTest.RANDOM.nextBoolean()) {
array.add(ArrayGenerator.generate(newSize));
} else {
array.add(ObjectGenerator.generate(newSize));
}
}
return array;
}
}
class PrimtiveGenerator {
static JsonNode.PrimitiveNode generate() {
int primitiveTypre = JsonParserTest.RANDOM.nextInt(10) + 1;
switch (primitiveTypre) {
case 1:
case 2:
case 3:
case 4:
return new JsonNode.PrimitiveNode(StringGenerator.generate());
case 5:
case 6:
case 7:
case 8:
return new JsonNode.PrimitiveNode(NumberGenerator.generate());
case 9:
return new JsonNode.PrimitiveNode(Boolean.toString(JsonParserTest.RANDOM.nextBoolean()));
case 10:
return null;
}
return null;
}
}
class ObjectGenerator {
static JsonNode.ObjectNode generate(int size) {
JsonNode.ObjectNode jobj = new JsonNode.ObjectNode();
if (size <= JsonParserTest.maxChildrenPerNode) {
for (int i = 0; i < size; i++) {
jobj.put(StringGenerator.generate(), PrimtiveGenerator.generate());
}
} else {
int newSize = size;
do {
int childSize = JsonParserTest.RANDOM.nextInt(newSize);
if (JsonParserTest.RANDOM.nextBoolean()) {
jobj.put(StringGenerator.generate(), ArrayGenerator.generate(childSize));
} else {
jobj.put(StringGenerator.generate(), ObjectGenerator.generate(childSize));
}
newSize = newSize - childSize;
} while (newSize > JsonParserTest.maxChildrenPerNode);
if (JsonParserTest.RANDOM.nextBoolean()) {
jobj.put(StringGenerator.generate(), ArrayGenerator.generate(newSize));
} else {
jobj.put(StringGenerator.generate(), ObjectGenerator.generate(newSize));
}
}
return jobj;
}
}
}