com.mysql.cj.xdevapi.JsonParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mysql-connector-java Show documentation
Show all versions of mysql-connector-java Show documentation
Fixed bugs repo for official MySQL JDBC connector
The newest version!
/*
* Copyright (c) 2015, 2020, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 2.0, as published by the
* Free Software Foundation.
*
* This program is also distributed with certain software (including but not
* limited to OpenSSL) that is licensed under separate terms, as designated in a
* particular file or component or in included license documentation. The
* authors of MySQL hereby grant you an additional permission to link the
* program and your derivative works with the separately licensed software that
* they have included with MySQL.
*
* Without limiting anything contained in the foregoing, this file, which is
* part of MySQL Connector/J, is also subject to the Universal FOSS Exception,
* version 1.0, a copy of which can be found at
* http://oss.oracle.com/licenses/universal-foss-exception.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
* for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.mysql.cj.xdevapi;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import com.mysql.cj.Messages;
import com.mysql.cj.exceptions.AssertionFailedException;
import com.mysql.cj.exceptions.ExceptionFactory;
import com.mysql.cj.exceptions.WrongArgumentException;
public class JsonParser {
enum Whitespace {
TAB('\u0009'), LF('\n'), CR('\r'), SPACE('\u0020');
public final char CHAR;
private Whitespace(char character) {
this.CHAR = character;
}
};
enum StructuralToken {
/**
* [ U+005B left square bracket
*/
LSQBRACKET('\u005B'),
/**
* ] U+005D right square bracket
*/
RSQBRACKET('\u005D'),
/**
* { U+007B left curly bracket
*/
LCRBRACKET('\u007B'),
/**
* } U+007D right curly bracket
*/
RCRBRACKET('\u007D'),
/**
* : U+003A colon
*/
COLON('\u003A'),
/**
* , U+002C comma
*/
COMMA('\u002C');
public final char CHAR;
private StructuralToken(char character) {
this.CHAR = character;
}
};
enum EscapeChar {
/**
* \" represents the quotation mark character (U+0022)
*/
QUOTE('\u0022', "\\\""),
/**
* \\ represents the reverse solidus character (U+005C)
*/
RSOLIDUS('\\', "\\\\"),
/**
* \/ represents the solidus character (U+002F)
*/
SOLIDUS('\u002F', "\\\u002F"),
/**
* \b represents the backspace character (U+0008)
*/
BACKSPACE('\u0008', "\\b"),
/**
* \f represents the form feed character (U+000C)
*/
FF('\u000C', "\\f"),
/**
* \n represents the line feed character (U+000A)
*/
LF('\n', "\\n"),
/**
* \r represents the carriage return character (U+000D)
*/
CR('\r', "\\r"),
/**
* \t represents the character tabulation character (U+0009)
*/
TAB('\t', "\\t");
public final char CHAR;
public final String ESCAPED;
private EscapeChar(char character, String escaped) {
this.CHAR = character;
this.ESCAPED = escaped;
}
};
static Set whitespaceChars = new HashSet<>();
static HashMap unescapeChars = new HashMap<>();
static {
for (EscapeChar ec : EscapeChar.values()) {
unescapeChars.put(ec.ESCAPED.charAt(1), ec.CHAR);
}
for (Whitespace ws : Whitespace.values()) {
whitespaceChars.add(ws.CHAR);
}
}
private static boolean isValidEndOfValue(char ch) {
return StructuralToken.COMMA.CHAR == ch || StructuralToken.RCRBRACKET.CHAR == ch || StructuralToken.RSQBRACKET.CHAR == ch;
}
/**
* Create {@link DbDoc} object from JSON string.
*
* @param jsonString
* JSON string representing a document
* @return New {@link DbDoc} object initialized by parsed JSON string.
*/
public static DbDoc parseDoc(String jsonString) {
try {
return JsonParser.parseDoc(new StringReader(jsonString));
} catch (IOException ex) {
throw AssertionFailedException.shouldNotHappen(ex);
}
}
/**
* Create {@link DbDoc} object from JSON string provided by reader.
*
* @param reader
* JSON string reader.
* @return
* New {@link DbDoc} object initialized by parsed JSON string.
* @throws IOException
* if can't read
*/
public static DbDoc parseDoc(StringReader reader) throws IOException {
DbDoc doc = new DbDocImpl();
JsonValue val;
int leftBrackets = 0;
int rightBrackets = 0;
int intch;
while ((intch = reader.read()) != -1) {
String key = null;
char ch = (char) intch;
if (ch == StructuralToken.LCRBRACKET.CHAR || ch == StructuralToken.COMMA.CHAR) {
if (ch == StructuralToken.LCRBRACKET.CHAR) {
leftBrackets++;
}
if ((key = nextKey(reader)) != null) {
try {
if ((val = nextValue(reader)) != null) {
doc.put(key, val);
} else {
reader.reset();
}
} catch (WrongArgumentException ex) {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.0", new String[] { key }), ex);
}
} else {
reader.reset();
}
} else if (ch == StructuralToken.RCRBRACKET.CHAR) {
rightBrackets++;
break;
} else {
if (!whitespaceChars.contains(ch)) {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.1", new Character[] { ch }));
}
}
}
if (leftBrackets == 0) {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.2"));
} else if (leftBrackets > rightBrackets) {
throw ExceptionFactory.createException(WrongArgumentException.class,
Messages.getString("JsonParser.3", new Character[] { StructuralToken.RCRBRACKET.CHAR }));
}
return doc;
}
/**
* Create {@link JsonArray} object from JSON string provided by reader.
*
* @param reader
* JSON string reader.
* @return
* New {@link JsonArray} object initialized by parsed JSON string.
* @throws IOException
* if can't read
*/
public static JsonArray parseArray(StringReader reader) throws IOException {
JsonArray arr = new JsonArray();
JsonValue val;
int openings = 0;
int intch;
while ((intch = reader.read()) != -1) {
char ch = (char) intch;
if (ch == StructuralToken.LSQBRACKET.CHAR || ch == StructuralToken.COMMA.CHAR) {
if (ch == StructuralToken.LSQBRACKET.CHAR) {
openings++;
}
if ((val = nextValue(reader)) != null) {
arr.add(val);
} else {
reader.reset();
}
} else if (ch == StructuralToken.RSQBRACKET.CHAR) {
openings--;
break;
} else if (!whitespaceChars.contains(ch)) {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.1", new Character[] { ch }));
}
}
if (openings > 0) {
throw ExceptionFactory.createException(WrongArgumentException.class,
Messages.getString("JsonParser.3", new Character[] { StructuralToken.RSQBRACKET.CHAR }));
}
return arr;
}
private static String nextKey(StringReader reader) throws IOException {
reader.mark(1);
JsonString val = parseString(reader);
if (val == null) {
reader.reset();
}
// find delimiter
int intch;
char ch = ' ';
while ((intch = reader.read()) != -1) {
ch = (char) intch;
if (ch == StructuralToken.COLON.CHAR) {
// key/value delimiter found
break;
} else if (ch == StructuralToken.RCRBRACKET.CHAR) {
// end of document
break;
} else if (!whitespaceChars.contains(ch)) {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.1", new Character[] { ch }));
}
}
if (ch != StructuralToken.COLON.CHAR && val != null && val.getString().length() > 0) {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.4", new String[] { val.getString() }));
}
return val != null ? val.getString() : null;
}
private static JsonValue nextValue(StringReader reader) throws IOException {
reader.mark(1);
int intch;
while ((intch = reader.read()) != -1) {
char ch = (char) intch;
if (ch == EscapeChar.QUOTE.CHAR) {
// String detected
reader.reset();
return parseString(reader);
} else if (ch == StructuralToken.LSQBRACKET.CHAR) {
// array detected
reader.reset();
return parseArray(reader);
} else if (ch == StructuralToken.LCRBRACKET.CHAR) {
// inner Object detected
reader.reset();
return parseDoc(reader);
} else if (ch == '\u002D' || (ch >= '\u0030' && ch <= '\u0039')) { // {-,0-9}
// Number detected
reader.reset();
return parseNumber(reader);
} else if (ch == JsonLiteral.TRUE.value.charAt(0)) {
// "true" literal detected
reader.reset();
return parseLiteral(reader);
} else if (ch == JsonLiteral.FALSE.value.charAt(0)) {
// "false" literal detected
reader.reset();
return parseLiteral(reader);
} else if (ch == JsonLiteral.NULL.value.charAt(0)) {
// "null" literal detected
reader.reset();
return parseLiteral(reader);
} else if (ch == StructuralToken.RSQBRACKET.CHAR) {
// empty array
return null;
} else if (!whitespaceChars.contains(ch)) {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.1", new Character[] { ch }));
}
reader.mark(1);
}
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.5"));
}
private static void appendChar(StringBuilder sb, char ch) {
if (sb == null) {
if (!whitespaceChars.contains(ch)) {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.6", new Character[] { ch }));
}
} else {
sb.append(ch);
}
}
/**
* Create {@link JsonString} object from JSON string provided by reader.
*
* @param reader
* JSON string reader.
* @return
* New {@link JsonString} object initialized by parsed JSON string or null
if no JSON string was found.
* @throws IOException
* if can't read
*/
static JsonString parseString(StringReader reader) throws IOException {
int quotes = 0;
boolean escapeNextChar = false;
StringBuilder sb = null; // stays null until starting quotation mark is found
int intch;
while ((intch = reader.read()) != -1) {
char ch = (char) intch;
if (escapeNextChar) {
if (unescapeChars.containsKey(ch)) {
appendChar(sb, unescapeChars.get(ch));
} else {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.7", new Character[] { ch }));
}
escapeNextChar = false;
} else if (ch == EscapeChar.QUOTE.CHAR) {
if (sb == null) {
// start of string detected
sb = new StringBuilder();
quotes++;
} else {
// end of string detected
quotes--;
break;
}
} else if (quotes == 0 && ch == StructuralToken.RCRBRACKET.CHAR) {
// end of document
break;
} else if (ch == EscapeChar.RSOLIDUS.CHAR) {
escapeNextChar = true;
} else {
appendChar(sb, ch);
}
}
if (quotes > 0) {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.3", new Character[] { EscapeChar.QUOTE.CHAR }));
}
return sb == null ? null : new JsonString().setValue(sb.toString());
}
/**
* Create {@link JsonNumber} object from JSON string provided by reader.
*
* @param reader
* JSON string reader.
* @return
* New {@link JsonNumber} object initialized by parsed JSON string.
* @throws IOException
* if can't read
*/
static JsonNumber parseNumber(StringReader reader) throws IOException {
StringBuilder sb = null;
char lastChar = ' ';
boolean hasFractionalPart = false;
boolean hasExponent = false;
int intch;
while ((intch = reader.read()) != -1) {
char ch = (char) intch;
if (sb == null) {
// number is still not found
if (ch == '\u002D') {
// first char of number is minus
sb = new StringBuilder();
sb.append(ch);
} else if (ch >= '\u0030' && ch <= '\u0039') {
// first char of number is digit
sb = new StringBuilder();
sb.append(ch);
} else if (!whitespaceChars.contains(ch)) {
// only white spaces are allowed before value
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.1", new Character[] { ch }));
}
} else if (ch == '\u002D') {
// '-' is allowed only on first position and after exponent character
if (lastChar == 'E' || lastChar == 'e') {
sb.append(ch);
} else {
throw ExceptionFactory.createException(WrongArgumentException.class,
Messages.getString("JsonParser.8", new Object[] { ch, sb.toString() }));
}
} else if (ch >= '\u0030' && ch <= '\u0039') { // 0-9
sb.append(ch);
} else if (ch == 'E' || ch == 'e') {
// exponent character is allowed only after a digit
if (lastChar >= '\u0030' && lastChar <= '\u0039') {
hasExponent = true;
sb.append(ch);
} else {
throw ExceptionFactory.createException(WrongArgumentException.class,
Messages.getString("JsonParser.8", new Object[] { ch, sb.toString() }));
}
} else if (ch == '\u002E') {
// '.' is allowed only once, after a digit and not in exponent part
if (hasFractionalPart) {
throw ExceptionFactory.createException(WrongArgumentException.class,
Messages.getString("JsonParser.10", new Object[] { ch, sb.toString() }));
}
if (hasExponent) {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.11"));
}
if (lastChar >= '\u0030' && lastChar <= '\u0039') {
hasFractionalPart = true;
sb.append(ch);
} else {
throw ExceptionFactory.createException(WrongArgumentException.class,
Messages.getString("JsonParser.8", new Object[] { ch, sb.toString() }));
}
} else if (ch == '\u002B') {
// '+' is allowed only once after exponent character
if (lastChar == 'E' || lastChar == 'e') {
sb.append(ch);
} else {
throw ExceptionFactory.createException(WrongArgumentException.class,
Messages.getString("JsonParser.8", new Object[] { ch, sb.toString() }));
}
} else if (whitespaceChars.contains(ch) || isValidEndOfValue(ch)) {
// any whitespace, comma or right bracket character means the end of Number in case we already placed something to buffer
reader.reset(); // set reader position to last number char
break;
} else {
// no other characters are allowed after value
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.1", new Character[] { ch }));
}
lastChar = ch;
// it's safe to mark() here because the "higher" level marks won't be reset() once we start reading a number
reader.mark(1);
}
if (sb == null || sb.length() == 0) {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.5"));
}
return new JsonNumber().setValue(sb.toString());
}
/**
* Create {@link JsonLiteral} object from JSON string provided by reader.
*
* @param reader
* JSON string reader.
* @return
* New {@link JsonLiteral} object initialized by parsed JSON string.
* @throws IOException
* if can't read
*/
static JsonLiteral parseLiteral(StringReader reader) throws IOException {
StringBuilder sb = null;
JsonLiteral res = null;
int literalIndex = 0;
int intch;
while ((intch = reader.read()) != -1) {
char ch = (char) intch;
if (sb == null) {
// literal is still not found
if (ch == JsonLiteral.TRUE.value.charAt(0)) {
// first char of "true" literal is found
res = JsonLiteral.TRUE;
sb = new StringBuilder();
sb.append(ch);
literalIndex++;
} else if (ch == JsonLiteral.FALSE.value.charAt(0)) {
// first char of "false" literal is found
res = JsonLiteral.FALSE;
sb = new StringBuilder();
sb.append(ch);
literalIndex++;
} else if (ch == JsonLiteral.NULL.value.charAt(0)) {
// first char of "null" literal is found
res = JsonLiteral.NULL;
sb = new StringBuilder();
sb.append(ch);
literalIndex++;
} else if (!whitespaceChars.contains(ch)) {
// only whitespace chars are allowed before value
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.1", new Character[] { ch }));
}
} else if (literalIndex < res.value.length() && ch == res.value.charAt(literalIndex)) {
sb.append(ch);
literalIndex++;
} else if (whitespaceChars.contains(ch) || isValidEndOfValue(ch)) {
// any whitespace, colon or right bracket character means the end of literal in case we already placed something to buffer
reader.reset(); // set reader position to last number char
break;
} else {
// no other characters are allowed after value
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.1", new Character[] { ch }));
}
// it's safe to mark() here because the "higher" level marks won't be reset() once we start reading a number
reader.mark(1);
}
if (sb == null) {
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.5"));
}
if (literalIndex == res.value.length()) {
return res;
}
throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("JsonParser.12", new String[] { sb.toString() }));
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy