/** Assigns {@code nextToken} based on the value of {@code nextValue}. */ private JsonToken decodeLiteral() throws IOException { if (valuePos == -1) { // it was too long to fit in the buffer so it can only be a string return JsonToken.STRING; } else if (valueLength == 4 && ('n' == buffer[valuePos] || 'N' == buffer[valuePos]) && ('u' == buffer[valuePos + 1] || 'U' == buffer[valuePos + 1]) && ('l' == buffer[valuePos + 2] || 'L' == buffer[valuePos + 2]) && ('l' == buffer[valuePos + 3] || 'L' == buffer[valuePos + 3])) { value = "null"; return JsonToken.NULL; } else if (valueLength == 4 && ('t' == buffer[valuePos] || 'T' == buffer[valuePos]) && ('r' == buffer[valuePos + 1] || 'R' == buffer[valuePos + 1]) && ('u' == buffer[valuePos + 2] || 'U' == buffer[valuePos + 2]) && ('e' == buffer[valuePos + 3] || 'E' == buffer[valuePos + 3])) { value = TRUE; return JsonToken.BOOLEAN; } else if (valueLength == 5 && ('f' == buffer[valuePos] || 'F' == buffer[valuePos]) && ('a' == buffer[valuePos + 1] || 'A' == buffer[valuePos + 1]) && ('l' == buffer[valuePos + 2] || 'L' == buffer[valuePos + 2]) && ('s' == buffer[valuePos + 3] || 'S' == buffer[valuePos + 3]) && ('e' == buffer[valuePos + 4] || 'E' == buffer[valuePos + 4])) { value = FALSE; return JsonToken.BOOLEAN; } else { value = stringPool.get(buffer, valuePos, valueLength); return decodeNumber(buffer, valuePos, valueLength); } }
/** * Unescapes the character identified by the character or characters that immediately follow a * backslash. The backslash '\' should have already been read. This supports both unicode escapes * "u000A" and two-character escapes "\n". * * @throws NumberFormatException if any unicode escape sequences are malformed. */ private char readEscapeCharacter() throws IOException { if (pos == limit && !fillBuffer(1)) { throw syntaxError("Unterminated escape sequence"); } char escaped = buffer[pos++]; switch (escaped) { case 'u': if (pos + 4 > limit && !fillBuffer(4)) { throw syntaxError("Unterminated escape sequence"); } String hex = stringPool.get(buffer, pos, 4); pos += 4; return (char) Integer.parseInt(hex, 16); case 't': return '\t'; case 'b': return '\b'; case 'n': return '\n'; case 'r': return '\r'; case 'f': return '\f'; case '\'': case '"': case '\\': default: return escaped; } }
/** * Returns the string up to but not including {@code quote}, unescaping any character escape * sequences encountered along the way. The opening quote should have already been read. This * consumes the closing quote, but does not include it in the returned string. * * @param quote either ' or ". * @throws NumberFormatException if any unicode escape sequences are malformed. */ private String nextString(char quote) throws IOException { StringBuilder builder = null; do { /* the index of the first character not yet appended to the builder. */ int start = pos; while (pos < limit) { int c = buffer[pos++]; if (c == quote) { if (skipping) { return "skipped!"; } else if (builder == null) { return stringPool.get(buffer, start, pos - start - 1); } else { builder.append(buffer, start, pos - start - 1); return builder.toString(); } } else if (c == '\\') { if (builder == null) { builder = new StringBuilder(); } builder.append(buffer, start, pos - start - 1); builder.append(readEscapeCharacter()); start = pos; } } if (builder == null) { builder = new StringBuilder(); } builder.append(buffer, start, pos - start); } while (fillBuffer(1)); throw syntaxError("Unterminated string"); }
/** * Reads the value up to but not including any delimiter characters. This does not consume the * delimiter character. * * @param assignOffsetsOnly true for this method to only set the valuePos and valueLength fields * and return a null result. This only works if the literal is short; a string is returned * otherwise. */ private String nextLiteral(boolean assignOffsetsOnly) throws IOException { StringBuilder builder = null; valuePos = -1; valueLength = 0; int i = 0; findNonLiteralCharacter: while (true) { for (; pos + i < limit; i++) { switch (buffer[pos + i]) { case '/': case '\\': case ';': case '#': case '=': checkLenient(); // fall-through case '{': case '}': case '[': case ']': case ':': case ',': case ' ': case '\t': case '\f': case '\r': case '\n': break findNonLiteralCharacter; } } /* * Attempt to load the entire literal into the buffer at once. If * we run out of input, add a non-literal character at the end so * that decoding doesn't need to do bounds checks. */ if (i < buffer.length) { if (fillBuffer(i + 1)) { continue; } else { buffer[limit] = '\0'; break; } } // use a StringBuilder when the value is too long. It must be an unquoted string. if (builder == null) { builder = new StringBuilder(); } builder.append(buffer, pos, i); valueLength += i; pos += i; i = 0; if (!fillBuffer(1)) { break; } } String result; if (assignOffsetsOnly && builder == null) { valuePos = pos; result = null; } else if (skipping) { result = "skipped!"; } else if (builder == null) { result = stringPool.get(buffer, pos, i); } else { builder.append(buffer, pos, i); result = builder.toString(); } valueLength += i; pos += i; return result; }