All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.bloodshura.ignitium.cfg.json.handling.JsonParser Maven / Gradle / Ivy

Go to download

A collection of configuration and serialization readers and writers, like JSON, internationalization (I18n), and CSV.

There is a newer version: 1.0.1
Show newest version
package com.github.bloodshura.ignitium.cfg.json.handling;

import com.github.bloodshura.ignitium.cfg.json.JsonArray;
import com.github.bloodshura.ignitium.cfg.json.JsonObject;
import com.github.bloodshura.ignitium.cfg.json.JsonParseException;
import com.github.bloodshura.ignitium.cfg.json.JsonParseException.ErrorType;
import com.github.bloodshura.ignitium.cfg.json.handling.Token.Type;
import com.github.bloodshura.ignitium.collection.store.impl.XStack;
import com.github.bloodshura.ignitium.resource.Resource;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.Reader;

public class JsonParser {
	@Nullable
	public static Object parse(@Nonnull Resource resource) throws IOException, JsonParseException {
		try (Reader reader = resource.newReader()) {
			Yylex lexer = new Yylex();

			lexer.yyreset(reader);

			XStack statuses = new XStack<>();
			XStack values = new XStack<>();
			int position;
			Status status = null;
			Token token;

			do {
				token = lexer.yylex();
				position = lexer.getPosition();

				if (status == null) {
					if (token.getType() == Type.VALUE) {
						statuses.push(status = Status.IN_FINISHED_VALUE);

						if (token.getValue() != null) {
							values.push(token.getValue());
						}
					} else if (token.getType() == Type.LEFT_BRACE) {
						statuses.push(status = Status.IN_OBJECT);
						values.push(new JsonObject());
					} else if (token.getType() == Type.LEFT_SQUARE) {
						statuses.push(status = Status.IN_ARRAY);
						values.push(new JsonArray());
					} else if (token.getType() == Type.END_OF_FILE) {
						return null;
					} else {
						status = Status.ERROR;
					}
				} else if (status == Status.IN_FINISHED_VALUE) {
					if (token.getType() == Type.END_OF_FILE) {
						return values.pop();
					}

					status = Status.ERROR;
				} else if (status == Status.IN_OBJECT) {
					if (token.getType() == Type.VALUE) {
						if (token.getValue() instanceof String) {
							statuses.push(status = Status.PASSED_PAIR_KEY);
							values.push(token.getValue());
						} else {
							status = Status.ERROR;
						}
					} else if (token.getType() == Type.RIGHT_BRACE) {
						if (values.size() > 1) {
							statuses.pop();
							values.pop();
							status = statuses.firstOr(Status.ERROR);
						} else {
							status = Status.IN_FINISHED_VALUE;
						}
					} else if (token.getType() != Type.COMMA) {
						status = Status.ERROR;
					}
				} else if (status == Status.PASSED_PAIR_KEY) {
					if (token.getType() == Type.VALUE) {
						String key = (String) values.pop(); // If status = PASSED_PAIR_KEY then last pushed item is String
						JsonObject object = (JsonObject) values.first();

						// Could just let it happen and JsonObject::set, when checking that the value is null,
						// would remove the key if it existed.
						// So, if we had a JSON for e.g. { "a": "b", "a": null },
						// that way it would REMOVE the 'a' key completely.
						// By previously checking and only calling JsonObject::set if the value is not-null,
						// on this case, "a" will remain "b".
						if (token.getValue() != null) {
							object.set(key, token.getValue());
						}

						statuses.pop();
						status = statuses.firstOr(Status.ERROR);
					} else if (token.getType() == Type.LEFT_SQUARE) {
						String key = (String) values.pop(); // If status = PASSED_PAIR_KEY then last pushed item is String
						JsonObject object = (JsonObject) values.first();
						JsonArray newArray = new JsonArray();

						object.set(key, newArray);
						statuses.pop();
						statuses.push(status = Status.IN_ARRAY);
						values.push(newArray);
					} else if (token.getType() == Type.LEFT_BRACE) {
						String key = (String) values.pop(); // If status = PASSED_PAIR_KEY then last pushed item is String
						JsonObject object = (JsonObject) values.first();
						JsonObject newObject = new JsonObject();

						object.set(key, newObject);
						statuses.pop();
						statuses.push(status = Status.IN_OBJECT);
						values.push(newObject);
					} else if (token.getType() != Type.COLON) {
						status = Status.ERROR;
					}
				} else if (status == Status.IN_ARRAY) {
					if (token.getType() == Type.VALUE) {
						JsonArray array = (JsonArray) values.first();

						if (token.getValue() != null) {
							array.add(token.getValue());
						}
					} else if (token.getType() == Type.RIGHT_SQUARE) {
						if (values.size() > 1) {
							statuses.pop();
							values.pop();
							status = statuses.firstOr(Status.ERROR);
						} else {
							status = Status.IN_FINISHED_VALUE;
						}
					} else if (token.getType() == Type.LEFT_BRACE) {
						JsonArray array = (JsonArray) values.first();
						JsonObject newObject = new JsonObject();

						array.add(newObject);
						statuses.push(status = Status.IN_OBJECT);
						values.push(newObject);
					} else if (token.getType() == Type.LEFT_SQUARE) {
						JsonArray array = (JsonArray) values.first();
						JsonArray newArray = new JsonArray();

						array.add(newArray);
						statuses.push(status = Status.IN_ARRAY);
						values.push(newArray);
					} else if (token.getType() != Type.COMMA) {
						status = Status.ERROR;
					}
				}

				if (status == Status.ERROR) {
					throw new JsonParseException(ErrorType.UNEXPECTED_TOKEN, position, token);
				}
			}
			while (token.getType() != Type.END_OF_FILE);

			throw new JsonParseException(ErrorType.UNEXPECTED_TOKEN, position, token);
		}
	}

	public static enum Status {
		END,
		ERROR,
		IN_ARRAY,
		IN_FINISHED_VALUE,
		IN_OBJECT,
		IN_PAIR_VALUE,
		PASSED_PAIR_KEY
	}
}