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

io.github.kits.json.tokenizer.JsonTokenizer Maven / Gradle / Ivy

The newest version!
package io.github.kits.json.tokenizer;

import io.github.kits.StringKit;
import io.github.kits.exception.JsonParseException;

import java.io.IOException;

import static io.github.kits.json.tokenizer.JsonTokenType.BEGIN_ARRAY;
import static io.github.kits.json.tokenizer.JsonTokenType.BEGIN_OBJECT;
import static io.github.kits.json.tokenizer.JsonTokenType.BOOLEAN;
import static io.github.kits.json.tokenizer.JsonTokenType.END_ARRAY;
import static io.github.kits.json.tokenizer.JsonTokenType.END_DOCUMENT;
import static io.github.kits.json.tokenizer.JsonTokenType.END_OBJECT;
import static io.github.kits.json.tokenizer.JsonTokenType.NULL;
import static io.github.kits.json.tokenizer.JsonTokenType.NUMBER;
import static io.github.kits.json.tokenizer.JsonTokenType.SEP_COLON;
import static io.github.kits.json.tokenizer.JsonTokenType.SEP_COMMA;
import static io.github.kits.json.tokenizer.JsonTokenType.STRING;

/**
 * @project: kits
 * @created: with IDEA
 * @author: whimthen
 * @date: 2019-03-05-11:18 | March. Tuesday
 */
public class JsonTokenizer {

	private CharReader charReader;

	private JsonTokenList jsonTokens;

	/**
	 * Parse a JSON string into a JsonTokenList
	 *
	 * @param json		JSON String
	 * @return			JsonTokenList
	 * @throws IOException	IOException
	 */
	public JsonTokenList tokenize(String json) throws IOException {
		if (StringKit.isNullOrEmpty(json)) {
			throw new JsonParseException("Json string is null or empty!");
		}
		this.charReader = new CharReader(json);
		this.jsonTokens = new JsonTokenList();
		jsonTokenize();

		return jsonTokens;
	}

	/**
	 * Parse characters into JsonToken
	 *
	 * @throws IOException	IOException
	 */
	private void jsonTokenize() throws IOException {
		// 使用do-while处理空文件
		JsonToken jsonToken;
		do {
			jsonToken = start();
			jsonTokens.add(jsonToken);
		} while (jsonToken.getTokenType() != END_DOCUMENT);
	}

	/**
	 * Parse and load each character in CharReader to the appropriate type
	 *
	 * @return		JsonToken
	 * @throws IOException	IOException
	 */
	private JsonToken start() throws IOException {
		char ch;
		do {
			if (!charReader.hasMore()) {
				return new JsonToken(END_DOCUMENT, null);
			}

			ch = charReader.next();
		} while (isWhiteSpace(ch));

		switch (ch) {
			case '{':
				return new JsonToken(BEGIN_OBJECT, String.valueOf(ch));
			case '}':
				return new JsonToken(END_OBJECT, String.valueOf(ch));
			case '[':
				return new JsonToken(BEGIN_ARRAY, String.valueOf(ch));
			case ']':
				return new JsonToken(END_ARRAY, String.valueOf(ch));
			case ',':
				return new JsonToken(SEP_COMMA, String.valueOf(ch));
			case ':':
				return new JsonToken(SEP_COLON, String.valueOf(ch));
			case 'n':
				return readNull();
			case 't':
			case 'f':
				return readBoolean();
			case '"':
				return readString();
			case '-':
				return readNumber();
		}

		if (isDigitOne2Nine(ch)) {
			return readNumber();
		}

		throw new JsonParseException("Illegal character");
	}

	/**
	 * Handling spaces or line breaks, etc.
	 *
	 * @param ch	Char
	 * @return		true-is space | false-not space
	 */
	private boolean isWhiteSpace(char ch) {
		return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n');
	}

	/**
	 * Read the character as a JsonToken of type String
	 * 
	 * @return	JsonToken
	 * @throws IOException IOException
	 */
	private JsonToken readString() throws IOException {
		StringBuilder sb = new StringBuilder();
		for (;;) {
			char ch = charReader.next();
			if (ch == '\\') {
				if (!isEscape()) {
					throw new JsonParseException("Invalid escape character");
				}
				sb.append('\\');
				ch = charReader.peek();
				sb.append(ch);
				if (ch == 'u') {
					for (int i = 0; i < 4; i++) {
						ch = charReader.next();
						if (isHex(ch)) {
							sb.append(ch);
						} else {
							throw new JsonParseException("Invalid character");
						}
					}
				}
			} else if (ch == '"') {
				return new JsonToken(STRING, sb.toString());
			} else if (ch == '\r' || ch == '\n') {
				throw new JsonParseException("Invalid character");
			} else {
				sb.append(ch);
			}
		}
	}

	/**
	 * Determine if it is a special character
	 * 
	 * @return	true-it's special | false-not special
	 * @throws IOException	IOException
	 */
	private boolean isEscape() throws IOException {
		char ch = charReader.next();
		return (ch == '"' || ch == '\\' || ch == 'u' || ch == 'r'
				|| ch == 'n' || ch == 'b' || ch == 't' || ch == 'f');

	}

	/**
	 * Determine if it is a hex number
	 * 
	 * @param ch	Char
	 * @return		true-is hex | false-not hex
	 */
	private boolean isHex(char ch) {
		return ((ch >= '0' && ch <= '9') || ('a' <= ch && ch <= 'f')
				|| ('A' <= ch && ch <= 'F'));
	}

	/**
	 * Read characters as a numeric type of JsonToken
	 * 
	 * @return	JsonToken
	 * @throws IOException IOException
	 */
	private JsonToken readNumber() throws IOException {
		char ch = charReader.peek();
		StringBuilder sb = new StringBuilder();
		if (ch == '-') {    // 处理负数
			sb.append(ch);
			ch = charReader.next();
			if (ch == '0') {    // 处理 -0.xxxx
				sb.append(ch);
				sb.append(readFracAndExp());
			} else if (isDigitOne2Nine(ch)) {
				addDigit(ch, sb);
			} else {
				throw new JsonParseException("Invalid minus number");
			}
		} else if (ch == '0') {    // 处理小数
			sb.append(ch);
			sb.append(readFracAndExp());
		} else {
			addDigit(ch, sb);
		}

		return new JsonToken(NUMBER, sb.toString());
	}

	private void addDigit(char ch, StringBuilder sb) throws IOException {
		do {
			sb.append(ch);
			ch = charReader.next();
		} while (isDigitOne2Nine(ch));
		if (ch != (char) -1) {
			charReader.back();
			sb.append(readFracAndExp());
		}
	}

	/**
	 * Determine whether it is a scientific notation
	 * 
	 * @param ch	Char
	 * @return		true-scientific | false-not scientific
	 */
	private boolean isExp(char ch) {
		return ch == 'e' || ch == 'E';
	}

	/**
	 * Determine if it is a number for 0-9
	 *
	 * @param ch	Char
	 * @return		true-number | false-not number
	 */
	private boolean isDigitOne2Nine(char ch) {
		return ch >= '0' && ch <= '9';
	}

	/**
	 * Reading floating number as String
	 *
	 * @return	String
	 * @throws IOException IOException
	 */
	private String readFracAndExp() throws IOException {
		StringBuilder sb = new StringBuilder();
		char ch = charReader.next();
		if (ch ==  '.') {
			sb.append(ch);
			ch = charReader.next();
			if (!isDigitOne2Nine(ch)) {
				throw new JsonParseException("Invalid frac");
			}
			do {
				sb.append(ch);
				ch = charReader.next();
			} while (isDigitOne2Nine(ch));

			if (isExp(ch)) {    // 处理科学计数法
				sb.append(ch);
				sb.append(readExp());
			} else {
				if (ch != (char) -1) {
					charReader.back();
				}
			}
		} else if (isExp(ch)) {
			sb.append(ch);
			sb.append(readExp());
		} else {
			charReader.back();
		}

		return sb.toString();
	}

	/**
	 * Reading scientific notation
	 *
	 * @return	String
	 * @throws IOException IOException
	 */
	private String readExp() throws IOException {
		StringBuilder sb = new StringBuilder();
		char ch = charReader.next();
		if (ch == '+' || ch =='-') {
			sb.append(ch);
			ch = charReader.next();
			if (isDigitOne2Nine(ch)) {
				do {
					sb.append(ch);
					ch = charReader.next();
				} while (isDigitOne2Nine(ch));

				if (ch != (char) -1) {    // 读取结束,不用回退
					charReader.back();
				}
			} else {
				throw new JsonParseException("e or E");
			}
		} else {
			throw new JsonParseException("e or E");
		}

		return sb.toString();
	}

	/**
	 * Reading boolean value as JsonToken
	 *
	 * @return	JsonToken
	 * @throws IOException IOException
	 */
	private JsonToken readBoolean() throws IOException {
		if (charReader.peek() == 't') {
			if (!(charReader.next() == 'r' && charReader.next() == 'u' && charReader.next() == 'e')) {
				throw new JsonParseException("Invalid json string");
			}

			return new JsonToken(BOOLEAN, "true");
		} else {
			if (!(charReader.next() == 'a' && charReader.next() == 'l'
					&& charReader.next() == 's' && charReader.next() == 'e')) {
				throw new JsonParseException("Invalid json string");
			}

			return new JsonToken(BOOLEAN, "false");
		}
	}

	/**
	 * Reading null value as JsonToken
	 *
	 * @return	JsonToken
	 * @throws IOException IOException
	 */
	private JsonToken readNull() throws IOException {
		if (!(charReader.next() == 'u' && charReader.next() == 'l' && charReader.next() == 'l')) {
			throw new JsonParseException("Invalid json string");
		}

		return new JsonToken(NULL, "null");
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy