
com.republicate.json.Json Maven / Gradle / Ivy
package com.republicate.json;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Consumer;
/**
* JSON container interface.
* The two inner classes Array
and Object
represent the two flavors of this container.
*/
public interface Json extends Serializable
{
/**
* Logger
*/
Logger logger = LoggerFactory.getLogger("json");
/**
* Indentation
*/
String INDENTATION = " ";
/**
* Parse a JSON string into a JSON container
* @param content JSON content
* @return parsed json
* @throws IOException if parsing fails
*/
static Json parse(String content) throws IOException
{
return new Parser(content).parse();
}
/**
* Parse a JSON stream into a JSON container
* @param reader JSON content reader
* @return parsed json
* @throws IOException if parsing fails
*/
static Json parse(Reader reader) throws IOException
{
return new Parser(reader).parse();
}
/**
* Parse a JSON stream into a JSON container or simple value
* @param content JSON content
* @return parsed json
* @throws IOException if parsing fails
*/
static Serializable parseValue(String content) throws IOException
{
return new Parser(content).parseValue(true);
}
/**
* Parse a JSON stream into a JSON container
* @param reader JSON content reader
* @return parsed json
* @throws IOException if parsing fails
*/
static Serializable parseValue(Reader reader) throws IOException
{
return new Parser(reader).parseValue(true);
}
/**
* Check if the underlying container is a JSON array.
* @return true if underlying container is an array, false otherwise
*/
boolean isArray();
/**
* Check if the underlying container is an object.
* @return true if underlying container is an object, false otherwise
*/
boolean isObject();
/**
* Ensure that the underlying container is an array.
* @throws IllegalStateException otherwise
*/
void ensureIsArray();
/**
* Ensure that the underlying container is an object.
* @throws IllegalStateException otherwise
*/
void ensureIsObject();
/**
* Get self as a Array
* @return self as a Jon.Array
* @throws IllegalStateException if container is not a Array
*/
default Json.Array asArray()
{
ensureIsArray();
return (Array) this;
}
/**
* Get self as a Array
* @return self as a Json.Object
* @throws IllegalStateException if container is not a Object
*/
default Json.Object asObject()
{
ensureIsObject();
return (Object) this;
}
/**
* Returns the number of elements in this collection.
* @return the number of elements in this collection
*/
int size();
/**
* Returns true if this collection contains no elements.
* @return true if this collection contains no elements
*/
boolean isEmpty();
/**
* Writes a representation of this container to the specified writer.
* @param writer target writer
* @return input writer
* @throws IOException if serialization failes
*/
Writer toString(Writer writer) throws IOException;
/**
* Writes a pretty representation of this container to the specified writer.
* @param writer target writer
* @param indent current indentation
* @return input writer
* @throws IOException if serialization failes
*/
Writer toPrettyString(Writer writer, String indent) throws IOException;
/**
* Gets a pretty representation of this container.
* @return input writer
*/
default String toPrettyString()
{
try
{
return toPrettyString(new StringWriter(), "").toString();
}
catch (IOException ioe)
{
logger.error("could not render Json container string", ioe);
return null;
}
}
/**
* Implements a JSON array
*/
class Array extends ArrayList implements Json
{
private static final long serialVersionUID = 1272604422260086506L;
/**
* Builds an empty Json.Array.
*/
public Array()
{
}
/**
* Builds a Json.Array with the content of an existing collection.
*/
public Array(Collection extends Serializable> collection)
{
super(collection);
}
/**
* Check if the underlying container is an array.
*
* @return true if underlying container is an array, false otherwise
*/
@Override
public boolean isArray()
{
return true;
}
/**
* Check if the underlying container is an object.
*
* @return true if underlying container is an object, false otherwise
*/
@Override
public boolean isObject()
{
return false;
}
/**
* Check that the underlying container is an array.
* @throws IllegalStateException otherwise
*/
@Override
public void ensureIsArray()
{
}
/**
* Check that the underlying container is an object.
* @throws IllegalStateException otherwise
*/
@Override
public void ensureIsObject()
{
throw new IllegalStateException("container must be a JSON object");
}
/**
* Writes a representation of this container to the specified writer.
* @param writer target writer
*/
public Writer toString(Writer writer) throws IOException
{
writer.write('[');
boolean first = true;
for (Serializable value : this)
{
if (first)
{
first = false;
}
else
{
writer.write(',');
}
if (value instanceof Json)
{
((Json)value).toString(writer);
}
else
{
Serializer.writeSerializable(value, writer);
}
}
writer.write(']');
return writer;
}
/**
* Writes a pretty representation of this container to the specified writer.
* @param writer target writer
* @return input writer
*/
@Override
public Writer toPrettyString(Writer writer, String indent) throws IOException
{
writer.write(indent);
String nextIndent = indent + INDENTATION;
writer.write("[\n");
boolean first = true;
for (Serializable value : this)
{
if (first)
{
first = false;
}
else
{
writer.write(",\n");
}
if (value instanceof Json)
{
((Json)value).toPrettyString(writer, nextIndent);
}
else
{
writer.write(nextIndent);
Serializer.writeSerializable(value, writer);
}
}
writer.write('\n');
writer.write(indent);
writer.write(']');
return writer;
}
/**
* Returns a string representation of this container
* @return container string representation
*/
@Override
public String toString()
{
try
{
return toString(new StringWriter()).toString();
}
catch (IOException ioe)
{
logger.error("could not render Array string", ioe);
return null;
}
}
/**
* Returns the element at the specified position as a String value.
* @param index index of the element to return
* @return the element at the specified position as a String value
*/
public String getString(int index)
{
return TypeUtils.toString(get(index));
}
/**
* Returns the element at the specified position as a Boolean value.
* @param index index of the element to return
* @return the element at the specified position as a Boolean value
*/
public Boolean getBoolean(int index)
{
return TypeUtils.toBoolean(get(index));
}
/**
* Returns the element at the specified position as a Character value.
* @param index index of the element to return
* @return the element at the specified position as a Character value
*/
public Character getChar(int index)
{
return TypeUtils.toChar(get(index));
}
/**
* Returns the element at the specified position as a Byte value.
* @param index index of the element to return
* @return the element at the specified position as a Byte value
*/
public Byte getByte(int index)
{
return TypeUtils.toByte(get(index));
}
/**
* Returns the element at the specified position as a Short value.
* @param index index of the element to return
* @return the element at the specified position as a Short value
*/
public Short getShort(int index)
{
return TypeUtils.toShort(get(index));
}
/**
* Returns the element at the specified position as a Integer value.
* @param index index of the element to return
* @return the element at the specified position as a Integer value
*/
public Integer getInteger(int index)
{
return TypeUtils.toInteger(get(index));
}
/**
* Returns the element at the specified position as a Long value.
* @param index index of the element to return
* @return the element at the specified position as a Long value
*/
public Long getLong(int index)
{
return TypeUtils.toLong(get(index));
}
/**
* Returns the element at the specified position as a BigInteger value.
* @param index index of the element to return
* @return the element at the specified position as a BigInteger value
*/
public BigInteger getBigInteger(int index)
{
return TypeUtils.toBigInteger(get(index));
}
/**
* Returns the element at the specified position as a Float value.
* @param index index of the element to return
* @return the element at the specified position as a Float value
*/
public Float getFloat(int index)
{
return TypeUtils.toFloat(get(index));
}
/**
* Returns the element at the specified position as a Double value.
* @param index index of the element to return
* @return the element at the specified position as a Double value
*/
public Double getDouble(int index)
{
return TypeUtils.toDouble(get(index));
}
/**
* Returns the element at the specified position as a BigDecimal value.
* @param index index of the element to return
* @return the element at the specified position as a BigDecimal value
*/
public BigDecimal getBigDecimal(int index)
{
return TypeUtils.toBigDecimal(get(index));
}
/**
* Returns the element at the specified position as a Date value.
* @param index index of the element to return
* @return the element at the specified position as a Date value
*/
public Date getDate(int index)
{
return TypeUtils.toDate(get(index));
}
/**
* Returns the element at the specified position as a Calendar value.
* @param index index of the element to return
* @return the element at the specified position as a Calendar value
*/
public Calendar getCalendar(int index)
{
return TypeUtils.toCalendar(get(index));
}
/**
* Returns the element at the specified position as a Json.Array value.
* @param index index of the element to return
* @return the element at the specified position as a Json.Array value
* @throws ClassCastException if value is not a a Jon.Array.
*/
public Json.Array getArray(int index)
{
Serializable value = get(index);
return (Json.Array)value;
}
/**
* Returns the element at the specified position as a Json.Object value.
* @param index index of the element to return
* @return the element at the specified position as a Json.Object value
* @throws ClassCastException if value is not a a Jon.Object.
*/
public Json.Object getObject(int index)
{
Serializable value = get(index);
return (Json.Object)value;
}
}
/**
* Implements a JSON object
*/
class Object extends LinkedHashMap implements Json, Iterable>
{
private static final long serialVersionUID = -8433114857911795160L;
/**
* Builds an emepty Json.Object.
*/
public Object()
{
}
/**
* Builds an object with the content of an existing Map
*/
public Object(Map extends String, ? extends Serializable> map)
{
super(map);
}
/**
* Check if the underlying container is an array.
*
* @return true if underlying container is an array, false otherwise
*/
@Override
public boolean isArray()
{
return false;
}
/**
* Check if the underlying container is an object.
*
* @return true if underlying container is an object, false otherwise
*/
@Override
public boolean isObject()
{
return true;
}
/**
* Check that the underlying container is an array.
* @throws IllegalStateException otherwise
*/
@Override
public void ensureIsArray()
{
throw new IllegalStateException("container must be a JSON array");
}
/**
* Check that the underlying container is an object.
* @throws IllegalStateException otherwise
*/
@Override
public void ensureIsObject()
{
}
/**
* Writes a representation of this container to the specified writer.
* @param writer target writer
*/
public Writer toString(Writer writer) throws IOException
{
writer.write('{');
boolean first = true;
for (Map.Entry entry : entrySet())
{
if (first)
{
first = false;
}
else
{
writer.write(',');
}
writer.write('"');
writer.write(entry.getKey());
writer.write("\":");
Serializable value = entry.getValue();
if (value instanceof Json)
{
((Json)value).toString(writer);
}
else
{
Serializer.writeSerializable(value, writer);
}
}
writer.write('}');
return writer;
}
/**
* Writes a pretty representation of this container to the specified writer.
* @param writer target writer
* @return input writer
*/
@Override
public Writer toPrettyString(Writer writer, String indent) throws IOException
{
writer.write("{\n");
String nextIndent = indent + INDENTATION;
boolean first = true;
for (Map.Entry entry : entrySet())
{
if (first)
{
first = false;
}
else
{
writer.write(",\n");
}
writer.write(nextIndent);
writer.write('"');
writer.write(entry.getKey());
writer.write("\" : ");
Serializable value = entry.getValue();
if (value instanceof Json)
{
((Json)value).toPrettyString(writer, nextIndent);
}
else
{
writer.write(nextIndent);
Serializer.writeSerializable(value, writer);
}
}
writer.write('\n');
writer.write(indent);
writer.write('}');
return writer;
}
/**
* Returns a string representation of this container
* @return container string representation
*/
@Override
public String toString()
{
try
{
return toString(new StringWriter()).toString();
}
catch (IOException ioe)
{
logger.error("could not render Array string", ioe);
return null;
}
}
/**
* Returns an iterator over map entries. Equivalent to entrySet().iterator()
.
*
* @return an Iterator.
*/
@Override
public Iterator> iterator()
{
return entrySet().iterator();
}
/**
* Performs the given action for each element of the {@code Iterable}
* until all elements have been processed or the action throws an
* exception.
* @param action The action to be performed for each element
*/
@Override
public void forEach(Consumer super Map.Entry> action)
{
entrySet().stream().forEach(action);
}
/**
* Creates a {@link Spliterator} over the elements described by this
* {@code Iterable}.
* @return a {@code Spliterator} over the elements described by this
* {@code Iterable}.
*/
@Override
public Spliterator> spliterator()
{
return Spliterators.spliterator(entrySet(), Spliterator.DISTINCT | Spliterator.SIZED | Spliterator.NONNULL | Spliterator.IMMUTABLE);
}
/**
* Returns the element under the specified key as a String value.
* @param key key of the element to return
* @return the element under the specified key as a String value or null if the key doesn't exist
*/
public String getString(String key)
{
return TypeUtils.toString(get(key));
}
/**
* Returns the element under the specified key as a Boolean value.
* @param key key of the element to return
* @return the element under the specified key as a Boolean value or null if the key doesn't exist
*/
public Boolean getBoolean(String key)
{
return TypeUtils.toBoolean(get(key));
}
/**
* Returns the element under the specified key as a Character value.
* @param key key of the element to return
* @return the element under the specified key as a Character value or null if the key doesn't exist
*/
public Character getChar(String key)
{
return TypeUtils.toChar(get(key));
}
/**
* Returns the element under the specified key as a Byte value.
* @param key key of the element to return
* @return the element under the specified key as a Byte value or null if the key doesn't exist
*/
public Byte getByte(String key)
{
return TypeUtils.toByte(get(key));
}
/**
* Returns the element under the specified key as a Short value.
* @param key key of the element to return
* @return the element under the specified key as a Short value or null if the key doesn't exist
*/
public Short getShort(String key)
{
return TypeUtils.toShort(get(key));
}
/**
* Returns the element under the specified key as a Integer value.
* @param key key of the element to return
* @return the element under the specified key as a Integer value or null if the key doesn't exist
*/
public Integer getInteger(String key)
{
return TypeUtils.toInteger(get(key));
}
/**
* Returns the element under the specified key as a Long value.
* @param key key of the element to return
* @return the element under the specified key as a Long value or null if the key doesn't exist
*/
public Long getLong(String key)
{
return TypeUtils.toLong(get(key));
}
/**
* Returns the element under the specified key as a BigInteger value.
* @param key key of the element to return
* @return the element under the specified key as a BigInteger value or null if the key doesn't exist
*/
public BigInteger getBigInteger(String key)
{
return TypeUtils.toBigInteger(get(key));
}
/**
* Returns the element under the specified key as a Float value.
* @param key key of the element to return
* @return the element under the specified key as a Float value or null if the key doesn't exist
*/
public Float getFloat(String key)
{
return TypeUtils.toFloat(get(key));
}
/**
* Returns the element under the specified key as a Double value.
* @param key key of the element to return
* @return the element under the specified key as a Double value or null if the key doesn't exist
*/
public Double getDouble(String key)
{
return TypeUtils.toDouble(get(key));
}
/**
* Returns the element under the specified key as a BigDecimal value.
* @param key key of the element to return
* @return the element under the specified key as a BigDecimal value or null if the key doesn't exist
*/
public BigDecimal getBigDecimal(String key)
{
return TypeUtils.toBigDecimal(get(key));
}
/**
* Returns the element under the specified key as a Date value.
* @param key key of the element to return
* @return the element under the specified key as a Date value or null if the key doesn't exist
*/
public Date getDate(String key)
{
return TypeUtils.toDate(get(key));
}
/**
* Returns the element under the specified key as a Calendar value.
* @param key key of the element to return
* @return the element under the specified key as a Calendar value or null if the key doesn't exist
*/
public Calendar getCalendar(String key)
{
return TypeUtils.toCalendar(get(key));
}
/**
* Returns the element under the specified key as a Json.Array value.
* @param key key of the element to return
* @return the element under the specified key as a Json.Array value or null if the key doesn't exist
* @throws ClassCastException if value is not a a Jon.Array.
*/
public Json.Array getArray(String key)
{
Serializable value = get(key);
return (Json.Array)value;
}
/**
* Returns the element under the specified key as a Json.Object value.
* @param key key of the element to return
* @return the element under the specified key as a Json.Object value or null if the key doesn't exist
* @throws ClassCastException if value is not a a Jon.Object.
*/
public Json.Object getObject(String key)
{
Serializable value = get(key);
return (Json.Object)value;
}
}
/**
* The Serializer class gathers static methods for JSON containers serialization.
*/
class Serializer
{
private static final String[] ESCAPED_CHARS;
static
{
ESCAPED_CHARS = new String[128];
for (int i = 0; i <= 0x1f; i++)
{
ESCAPED_CHARS[i] = String.format("\\u%04x", (int) i);
}
ESCAPED_CHARS['"'] = "\\\"";
ESCAPED_CHARS['\\'] = "\\\\";
ESCAPED_CHARS['\t'] = "\\t";
ESCAPED_CHARS['\b'] = "\\b";
ESCAPED_CHARS['\n'] = "\\n";
ESCAPED_CHARS['\r'] = "\\r";
ESCAPED_CHARS['\f'] = "\\f";
}
/**
* Escape a string for Json special characters towards
* the provided writer
* @param str input string
* @param writer target writer
* @return input writer
* @throws IOException if escaping fails
*/
static public Writer escapeJson(String str, Writer writer) throws IOException
{
// use com.google.gson.stream.JsonWriter method to minimize write() calls
// in case the output writer is not buffered
int last = 0;
int len = str.length();
for (int i = 0; i < len; ++i)
{
char c = str.charAt(i);
String escaped;
if (c < 128)
{
escaped = ESCAPED_CHARS[c];
if (escaped == null)
{
continue;
}
}
else if (c == '\u2028')
{
escaped = "\\u2028";
}
else if (c == '\u2029')
{
escaped = "\\u2029";
}
else
{
continue;
}
if (last < i)
{
writer.write(str, last, i - last);
}
writer.write(escaped);
last = i + 1;
}
if (last < len)
{
writer.write(str, last, len - last);
}
return writer;
}
/**
* Write a serializable element to an output writer
* @param serializable input element
* @param writer output writer
* @throws IOException if serialization fails
*/
static protected void writeSerializable(Serializable serializable, Writer writer) throws IOException
{
if (serializable == null)
{
writer.write("null");
}
else if (serializable instanceof Boolean)
{
writer.write(serializable.toString());
}
else if(serializable instanceof Number)
{
String number = ((Number)serializable).toString();
if (number.equals("-Infinity") || number.equals("Infinity") || number.equals("NaN"))
{
throw new IOException("invalid number: " + number);
}
writer.write(serializable.toString());
}
else
{
writer.write('\"');
escapeJson(serializable.toString(), writer);
writer.write('\"');
}
}
}
/**
* JSON parser.
*/
class Parser
{
private Reader reader;
private int row = 1;
private int col = 0;
private int ch = 0;
private boolean prefetch = false;
private int prefetched = 0;
private char buffer[] = new char[1024];
private int pos = 0;
private Parser(String content)
{
this(new FastStringReader(content));
}
private Parser(Reader reader)
{
/*
We need a reader that has an internal buffer otherwise read() calls
are gonna become a performance bottleneck. The markSuported() method
is a good indicator.
*/
if (reader.markSupported())
{
this.reader = reader;
}
else
{
this.reader = new BufferedReader(reader);
}
}
private int next() throws IOException
{
if (prefetch)
{
ch = prefetched;
prefetch = false;
}
else
{
ch = reader.read();
if (ch == '\n')
{
++row;
col = 0;
}
else
{
++col;
}
}
return ch;
}
private void back() throws IOException
{
if (prefetch)
{
throw error("internal error: cannot go back twice");
}
prefetch = true;
prefetched = ch;
}
private Json parse() throws IOException
{
Json ret = null;
skipWhiteSpace();
switch (ch)
{
case -1:
break;
case '{':
ret = parseObject();
break;
case '[':
ret = parseArray();
break;
default:
throw error("expecting '[' or '{', got: '" + display(ch) + "'");
}
if (ret != null)
{
skipWhiteSpace();
if (ch != -1)
{
throw error("expecting end of stream");
}
}
return ret;
}
private void skipWhiteSpace() throws IOException
{
for(; Character.isWhitespace(next()); );
}
private IOException error(String msg)
{
msg = "JSON parsing error at line " + row + ", column " + col + ": " + msg;
logger.error(msg);
return new IOException(msg);
}
private String display(int c)
{
if (c == -1)
{
return "end of stream";
}
else if (Character.isISOControl(c))
{
return "0x" + Integer.toHexString(c);
}
else
{
return String.valueOf((char)c);
}
}
private Array parseArray() throws IOException
{
Array ret = new Array();
skipWhiteSpace();
if (ch != ']')
{
back();
main:
while (true)
{
ret.add(parseValue());
skipWhiteSpace();
switch (ch)
{
case ']': break main;
case ',': break;
default: throw error("expecting ',' or ']', got: '" + display(ch) + "'");
}
}
}
return ret;
}
private Object parseObject() throws IOException
{
Object ret = new Object();
skipWhiteSpace();
if (ch != '}')
{
main:
while (true)
{
if (ch != '"')
{
throw error("expecting key string, got: '" + display(ch) + "'");
}
String key = parseString();
skipWhiteSpace();
if (ch != ':')
{
throw error("expecting ':', got: '" + display(ch) + "'");
}
Serializable value = parseValue();
Serializable previous = ret.put(key, value);
if (previous != null)
{
logger.warn("key '{}' is not unique at line {}, column {}", key, row, col);
}
skipWhiteSpace();
switch (ch)
{
case '}': break main;
case ',': break;
default: throw error("expecting ',' or '}', got: '" + display(ch) + "'");
}
skipWhiteSpace();
}
}
return ret;
}
private Serializable parseValue() throws IOException
{
return parseValue(false);
}
private Serializable parseValue(boolean complete) throws IOException
{
Serializable ret = null;
skipWhiteSpace();
if (ch == -1)
{
throw error("unexpecting end of stream");
}
switch (ch)
{
case '"': ret = parseString(); break;
case '[': ret = parseArray(); break;
case '{': ret = parseObject(); break;
case 't': ret = parseKeyword("true", true); break;
case 'f': ret = parseKeyword("false", false); break;
case 'n': ret = parseKeyword("null", null); break;
case '-': case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
ret = parseNumber(); break;
case -1: break;
default: throw error("unexpected chararcter: '" + display(ch) + "'");
}
if (complete)
{
skipWhiteSpace();
if (ch != -1)
{
throw error("expecting end of stream");
}
}
return ret;
}
private Serializable parseKeyword(String keyword, Serializable value) throws IOException
{
for (int i = 0; i < keyword.length(); ++i)
{
if (i > 0)
{
next();
}
if (ch != keyword.charAt(i))
{
if (ch == -1)
{
throw new IOException("encountered end of stream while parsing keyword '" + keyword + "'");
}
else
{
throw new IOException("invalid character '" + display(ch) + "' while parsing keyword '" + keyword + "'");
}
}
}
return value;
}
private String parseString() throws IOException
{
// borrow some optimization ideas from com.google.gson.stream.JsonReader
pos = 0;
StringBuilder builder = null;
while (true)
{
while (pos < buffer.length)
{
buffer[pos++] = (char)next();
if (ch == '"')
{
if (builder == null)
{
return new String(buffer, 0, pos - 1);
}
else
{
builder.append(buffer, 0, pos - 1);
return builder.toString();
}
}
else if (ch == '\\')
{
if (builder == null)
{
builder = new StringBuilder(Math.max(2 * pos, 16));
}
builder.append(buffer, 0, pos - 1);
pos = 0;
char c = parseEscapeSequence();
builder.append(c);
if (Character.isHighSurrogate(c))
{
ch = next();
if (ch != '\\')
{
throw error("low surrogate escape sequence expected");
}
c = parseEscapeSequence();
builder.append(c);
if (!Character.isLowSurrogate(c))
{
throw error("low surrogate escape sequence expected");
}
}
else if (Character.isLowSurrogate(c))
{
throw error("lone low surrogate escape sequence unexpected");
}
}
else if (ch < 0x20)
{
throw error("unescaped control character");
}
}
if (builder == null)
{
builder = new StringBuilder(Math.max(2 * pos, 16));
}
builder.append(buffer, 0, pos);
pos = 0;
if (next() == -1)
{
throw error("unterminated string");
}
}
}
private char parseEscapeSequence() throws IOException
{
switch (next())
{
case -1:
throw error("unterminated escape sequence");
case 'u':
char result = 0;
for (int i = 0; i < 4; ++i)
{
if (next() == -1)
{
throw error("unterminated escape sequence");
}
char c = (char)ch;
result <<= 4;
if (c >= '0' && c <= '9')
{
result += c - '0';
}
else if (c >= 'a' && c <= 'f')
{
result += c - 'a' + 10;
}
else if (c >= 'A' && c <= 'F')
{
result += c - 'A' + 10;
}
else
{
throw error("malformed escape sequence");
}
}
return result;
case 't':
return '\t';
case 'b':
return '\b';
case 'n':
return '\n';
case 'f':
return '\f';
case 'r':
return '\r';
case '"':
return '"';
case '\\':
return '\\';
case '/':
return '/';
default:
throw error("unknown escape sequence");
}
}
private static long MIN_LONG_DECILE = Long.MIN_VALUE / 10;
private Number parseNumber() throws IOException
{
// inspired from com.google.gson.stream.JsonReader, but much more readable
// and handle Double/BigDecimal alternatives
Number number;
pos = 0;
int digits = 0;
boolean negative = false;
boolean decimal = false;
boolean fitsInLong = true;
boolean fitsInDouble = true;
long negValue = 0;
// sign
if (ch == '-')
{
negative = true;
buffer[pos++] = (char)ch;
if (next() == -1)
{
throw error("malformed number");
}
}
// mantissa
digits += readDigits(false);
// fractional part
if (ch == '.')
{
decimal = true;
buffer[pos++] = (char)ch;
if (next() == -1)
{
throw error("malformed number");
}
digits += readDigits(true);
}
else if (ch != 'e' && ch != 'E')
{
// check if number fits in long
int i = negative ? 1 : 0;
negValue = -(buffer[i++] - '0');
for (; i < pos; ++i)
{
long newNegValue = negValue * 10 - (buffer[i] - '0');
fitsInLong &= negValue > MIN_LONG_DECILE
|| (negValue == MIN_LONG_DECILE && newNegValue < negValue);
if (!fitsInLong)
{
break;
}
negValue = newNegValue;
}
}
if (digits > 15)
{
fitsInDouble = false;
}
// exponent
if (ch == 'e' || ch == 'E')
{
decimal = true;
buffer[pos++] = (char)ch;
if (next() == -1)
{
throw error("malformed number");
}
if (pos == buffer.length)
{
throw error("number is too long at my taste");
}
if (ch == '+' || ch == '-')
{
buffer[pos++] = (char)ch;
if (next() == -1)
{
throw error("malformed number");
}
}
int expPos = pos;
int expDigits = readDigits(true); // or false ?
if (fitsInDouble && expDigits >= 3
&& (expDigits > 3
|| buffer[expPos] > '3'
|| buffer[expPos + 1] > '0'
|| buffer[expPos + 2] > '7'))
{
fitsInDouble = false;
}
}
if (!decimal && fitsInLong && (negative || negValue != Long.MIN_VALUE) && (!negative || negValue != 0))
{
number = Long.valueOf(-negValue);
}
else
{
String strBuff = new String(buffer, 0, pos);
if (!decimal)
{
number = new BigInteger(strBuff);
}
else if (fitsInDouble)
{
number = Double.valueOf(strBuff);
}
else
{
number = new BigDecimal(strBuff);
}
}
// we always end up reading one more character
back();
return number;
}
private int readDigits(boolean zeroFirstAllowed) throws IOException
{
int len = 0;
while (pos < buffer.length)
{
if (!Character.isDigit(ch))
{
break;
}
buffer[pos++] = (char)ch;
++len;
next();
}
if (pos == buffer.length)
{
throw error("number is too long at my taste");
}
if (len == 0 || !zeroFirstAllowed && len > 1 && buffer[pos - len] == '0')
{
throw error("malformed number");
}
return len;
}
}
/**
* Like a StringReader, but without any synchronization lock.
*/
class FastStringReader extends Reader
{
String str;
int len;
int pos = 0;
protected FastStringReader(String str)
{
this.str = str;
len = str.length();
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException
{
throw new NotImplementedException();
}
@Override
public boolean markSupported()
{
// That's an obvious lie. We don't. See Parser.Parser.parse(Reader) to see why.
return true;
}
@Override
public void close() throws IOException
{
}
@Override
public int read()
{
return pos == len ? -1 : str.charAt(pos++);
}
}
/**
* Conversion helpers
*/
class TypeUtils
{
private static String toString(java.lang.Object value)
{
return value == null ? null : value.toString();
}
private static Character toChar(java.lang.Object value)
{
if (value == null)
{
return null;
}
if (value instanceof Character)
{
return (Character)value;
}
if (value instanceof Boolean)
{
return ((Boolean)value).booleanValue()
? 't'
: 'f';
}
if (value instanceof String && ((String) value).length() == 1)
{
return ((String)value).charAt(0);
}
return null;
}
private static Boolean toBoolean(java.lang.Object value)
{
if (value == null)
{
return null;
}
if (value instanceof Boolean)
{
return (Boolean)value;
}
if (value instanceof String)
{
String str = (String)value;
if ("true".equals(str))
{
return true;
}
if ("false".equals(str))
{
return false;
}
try
{
value = Long.valueOf(str);
}
catch (NumberFormatException nfe)
{
return false;
}
}
if (value instanceof Number)
{
return ((Number)value).longValue() != 0l;
}
return false;
}
private static Byte toByte(java.lang.Object value)
{
if (value == null)
{
return null;
}
if (value instanceof Number)
{
return ((Number)value).byteValue();
}
if (value instanceof String)
{
try
{
return Byte.valueOf((String)value);
}
catch (NumberFormatException nfe)
{
}
}
return null;
}
private static Short toShort(java.lang.Object value)
{
if (value == null)
{
return null;
}
if (value instanceof Number)
{
return ((Number)value).shortValue();
}
if (value instanceof String)
{
try
{
return Short.valueOf((String)value);
}
catch (NumberFormatException nfe)
{
}
}
return null;
}
private static Integer toInteger(java.lang.Object value)
{
if (value == null)
{
return null;
}
if (value instanceof Number)
{
return ((Number)value).intValue();
}
if (value instanceof String)
{
try
{
return Integer.valueOf((String)value);
}
catch (NumberFormatException nfe)
{
}
}
return null;
}
private static Long toLong(java.lang.Object value)
{
if (value == null)
{
return null;
}
if (value instanceof Number)
{
return ((Number)value).longValue();
}
if (value instanceof String)
{
try
{
return Long.valueOf((String)value);
}
catch (NumberFormatException nfe)
{
}
}
return null;
}
private static BigInteger toBigInteger(java.lang.Object value)
{
if (value == null)
{
return null;
}
if (value instanceof BigInteger)
{
return ((BigInteger)value);
}
if (value instanceof Number)
{
return BigInteger.valueOf(((Number)value).longValue());
}
if (value instanceof String)
{
return new BigInteger((String) value);
}
return null;
}
private static Float toFloat(java.lang.Object value)
{
if (value == null)
{
return null;
}
if (value instanceof Number)
{
return ((Number)value).floatValue();
}
if (value instanceof String)
{
try
{
return Float.valueOf((String)value);
}
catch (NumberFormatException nfe)
{
}
}
return null;
}
private static Double toDouble(java.lang.Object value)
{
if (value == null)
{
return null;
}
if (value instanceof Number)
{
return ((Number)value).doubleValue();
}
if (value instanceof String)
{
try
{
return Double.valueOf((String)value);
}
catch (NumberFormatException nfe)
{
}
}
return null;
}
private static BigDecimal toBigDecimal(java.lang.Object value)
{
if (value == null)
{
return null;
}
if (value instanceof BigDecimal)
{
return ((BigDecimal)value);
}
if (value instanceof Number)
{
return BigDecimal.valueOf(((Number)value).doubleValue());
}
if (value instanceof String)
{
return new BigDecimal((String) value);
}
return null;
}
private static Date toDate(java.lang.Object value)
{
if (value == null || value instanceof Date)
{
return (Date)value;
}
if (value instanceof Calendar)
{
return ((Calendar)value).getTime();
}
return null;
}
private static Calendar toCalendar(java.lang.Object value)
{
if (value == null || value instanceof Calendar)
{
return (Calendar)value;
}
if (value instanceof Date)
{
// CB TODO - use model locale
Calendar calendar = GregorianCalendar.getInstance();
calendar.setTime((Date)value);
return calendar;
}
return null;
}
private static byte[] toBytes(java.lang.Object value)
{
if (value == null || value instanceof byte[])
{
return (byte[])value;
}
return String.valueOf(value).getBytes(StandardCharsets.UTF_8);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy