remove JSON parser, use a smaller JSON builder for JSON writing, add creation/modification date methods to MarcRecord

This commit is contained in:
Jörg Prante 2023-01-29 21:45:02 +01:00
parent 09db520c2e
commit 3c5b4bee3d
31 changed files with 399 additions and 6372 deletions

View file

@ -47,11 +47,3 @@ https://github.com/marc4j/marc4j/tree/master/test/resources
and were donated by libraries for testing purpose. and were donated by libraries for testing purpose.
----------------- -----------------
The JSON reader/writer classes are derived work from
https://github.com/ralfstx/minimal-json
The original work is based on the MIT License
-----------------

View file

@ -187,6 +187,28 @@ public class MarcField implements Comparable<MarcField> {
return builder.value; return builder.value;
} }
/**
* Return a "top most" value, useful for recovering a control field value from
* hierarchical structures like JSON formats.
* @return the value
*/
public String recoverControlFieldValue() {
String value = getValue();
if (value == null || value.isEmpty()) {
// the control field is disguised as a data field, try lookup value in first subfield of "_"
value = getFirstSubfieldValue("_");
// if no value, maybe blank " "?
if (value == null || value.isEmpty()) {
value = getFirstSubfieldValue(" ");
}
// still no value? Then it is some exotic like MAB with subfield "a"?
if (value == null || value.isEmpty()) {
value = getFirstSubfieldValue("a");
}
}
return value;
}
/** /**
* Returns if this MARC field is a control field. * Returns if this MARC field is a control field.
* @return true if control field, false if not * @return true if control field, false if not

View file

@ -174,7 +174,7 @@ public class MarcRecord implements Map<String, Object> {
public LocalDate getCreationDate() { public LocalDate getCreationDate() {
if (marcFields != null) { if (marcFields != null) {
String value = getFirst("008").getValue(); String value = getFirst("008").recoverControlFieldValue();
if (value != null && value.length() >= 6) { if (value != null && value.length() >= 6) {
return LocalDate.parse(value.substring(0, 6), field8DateFormat); return LocalDate.parse(value.substring(0, 6), field8DateFormat);
} }
@ -184,7 +184,7 @@ public class MarcRecord implements Map<String, Object> {
public LocalDate getLastModificationDate() { public LocalDate getLastModificationDate() {
if (marcFields != null) { if (marcFields != null) {
String value = getFirst("005").getValue(); String value = getFirst("005").recoverControlFieldValue();
if (value != null && value.length() >= 8) { if (value != null && value.length() >= 8) {
return LocalDate.parse(value.substring(0, 8), field5DateFormat); return LocalDate.parse(value.substring(0, 8), field5DateFormat);
} }

View file

@ -1,279 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.Objects;
/**
* This class serves as the entry point to the JSON API.
* <p>
* To <strong>parse</strong> a given JSON input, use the <code>parse()</code> methods like in this
* example:
* </p>
* <pre>
* JsonObject object = Json.parse(string).asObject();
* </pre>
* <p>
* To <strong>create</strong> a JSON data structure to be serialized, use the methods
* <code>value()</code>, <code>array()</code>, and <code>object()</code>. For example, the following
* snippet will produce the JSON string <em>{"foo": 23, "bar": true}</em>:
* </p>
* <pre>
* String string = Json.object().add("foo", 23).add("bar", true).toString();
* </pre>
* <p>
* To create a JSON array from a given Java array, you can use one of the <code>array()</code>
* methods with varargs parameters:
* </p>
* <pre>
* String[] names = ...
* JsonArray array = Json.array(names);
* </pre>
*/
public final class Json {
private Json() {
// not meant to be instantiated
}
/**
* Returns a JsonValue instance that represents the given <code>int</code> value.
*
* @param value the value to get a JSON representation for
* @return a JSON value that represents the given value
*/
public static JsonValue of(int value) {
return new JsonNumber(Integer.toString(value, 10));
}
/**
* Returns a JsonValue instance that represents the given <code>long</code> value.
*
* @param value the value to get a JSON representation for
* @return a JSON value that represents the given value
*/
public static JsonValue of(long value) {
return new JsonNumber(Long.toString(value, 10));
}
/**
* Returns a JsonValue instance that represents the given <code>float</code> value.
*
* @param value the value to get a JSON representation for
* @return a JSON value that represents the given value
*/
public static JsonValue of(float value) {
if (Float.isInfinite(value) || Float.isNaN(value)) {
throw new IllegalArgumentException("Infinite and NaN values not permitted in JSON");
}
return new JsonNumber(cutOffPointZero(Float.toString(value)));
}
/**
* Returns a JsonValue instance that represents the given <code>double</code> value.
*
* @param value the value to get a JSON representation for
* @return a JSON value that represents the given value
*/
public static JsonValue of(double value) {
if (Double.isInfinite(value) || Double.isNaN(value)) {
throw new IllegalArgumentException("Infinite and NaN values not permitted in JSON");
}
return new JsonNumber(cutOffPointZero(Double.toString(value)));
}
/**
* Returns a JsonValue instance that represents the given string.
*
* @param string the string to get a JSON representation for
* @return a JSON value that represents the given string
*/
public static JsonValue of(String string) {
return string == null ? JsonLiteral.NULL : new JsonString(string);
}
/**
* Returns a JsonValue instance that represents the given <code>boolean</code> value.
*
* @param value the value to get a JSON representation for
* @return a JSON value that represents the given value
*/
public static JsonValue of(boolean value) {
return value ? JsonLiteral.TRUE : JsonLiteral.FALSE;
}
/**
* Creates a new empty JsonArray. This is equivalent to creating a new JsonArray using the
* constructor.
*
* @return a new empty JSON array
*/
public static JsonArray array() {
return new JsonArray();
}
/**
* Creates a new JsonArray that contains the JSON representations of the given <code>int</code>
* values.
*
* @param values the values to be included in the new JSON array
* @return a new JSON array that contains the given values
*/
public static JsonArray array(int... values) {
Objects.requireNonNull(values);
JsonArray array = new JsonArray();
for (int value : values) {
array.add(value);
}
return array;
}
/**
* Creates a new JsonArray that contains the JSON representations of the given <code>long</code>
* values.
*
* @param values the values to be included in the new JSON array
* @return a new JSON array that contains the given values
*/
public static JsonArray array(long... values) {
Objects.requireNonNull(values);
JsonArray array = new JsonArray();
for (long value : values) {
array.add(value);
}
return array;
}
/**
* Creates a new JsonArray that contains the JSON representations of the given <code>float</code>
* values.
*
* @param values the values to be included in the new JSON array
* @return a new JSON array that contains the given values
*/
public static JsonArray array(float... values) {
Objects.requireNonNull(values);
JsonArray array = new JsonArray();
for (float value : values) {
array.add(value);
}
return array;
}
/**
* Creates a new JsonArray that contains the JSON representations of the given <code>double</code>
* values.
*
* @param values the values to be included in the new JSON array
* @return a new JSON array that contains the given values
*/
public static JsonArray array(double... values) {
Objects.requireNonNull(values);
JsonArray array = new JsonArray();
for (double value : values) {
array.add(value);
}
return array;
}
/**
* Creates a new JsonArray that contains the JSON representations of the given
* <code>boolean</code> values.
*
* @param values the values to be included in the new JSON array
* @return a new JSON array that contains the given values
*/
public static JsonArray array(boolean... values) {
Objects.requireNonNull(values);
JsonArray array = new JsonArray();
for (boolean value : values) {
array.add(value);
}
return array;
}
/**
* Creates a new JsonArray that contains the JSON representations of the given strings.
*
* @param strings the strings to be included in the new JSON array
* @return a new JSON array that contains the given strings
*/
public static JsonArray array(String... strings) {
Objects.requireNonNull(strings);
JsonArray array = new JsonArray();
for (String value : strings) {
array.add(value);
}
return array;
}
/**
* Creates a new empty JsonObject. This is equivalent to creating a new JsonObject using the
* constructor.
*
* @return a new empty JSON object
*/
public static JsonObject object() {
return new JsonObject();
}
/**
* Parses the given input string as JSON. The input must contain a valid JSON value, optionally
* padded with whitespace.
*
* @param string the input string, must be valid JSON
* @return a value that represents the parsed JSON
* @throws IOException if the input is not valid JSON
*/
public static JsonValue parse(String string) throws IOException {
Objects.requireNonNull(string);
JsonDefaultHandler handler = new JsonDefaultHandler();
new JsonReader<>(new StringReader(string), handler).parse();
return handler.getValue();
}
/**
* Reads the entire input from the given reader and parses it as JSON. The input must contain a
* valid JSON value, optionally padded with whitespace.
* <p>
* Characters are read in chunks into an input buffer. Hence, wrapping a reader in an additional
* <code>BufferedReader</code> likely won't improve reading performance.
* </p>
*
* @param reader the reader to read the JSON value from
* @return a value that represents the parsed JSON
* @throws IOException if an I/O error occurs in the reader
* @throws JsonException if the input is not valid JSON
*/
public static JsonValue parse(Reader reader) throws IOException {
JsonDefaultHandler handler = new JsonDefaultHandler();
try (reader) {
new JsonReader<>(reader, handler).parse();
}
return handler.getValue();
}
private static String cutOffPointZero(String string) {
if (string.endsWith(".0")) {
return string.substring(0, string.length() - 2);
}
return string;
}
}

View file

@ -1,397 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
/**
* Represents a JSON array, an ordered collection of JSON values.
* <p>
* Elements can be added using the <code>add(...)</code> methods which accept instances of
* {@link JsonValue}, strings, primitive numbers, and boolean values. To replace an element of an
* array, use the <code>set(int, ...)</code> methods.
* </p>
* <p>
* Elements can be accessed by their index using {@link #get(int)}. This class also supports
* iterating over the elements in document order using an {@link #iterator()} or an enhanced for
* loop:
* </p>
* <pre>
* for (JsonValue value : jsonArray) {
* ...
* }
* </pre>
* <p>
* An equivalent {@link List} can be obtained from the method {@link #values()}.
* </p>
* <p>
* Note that this class is <strong>not thread-safe</strong>. If multiple threads access a
* <code>JsonArray</code> instance concurrently, while at least one of these threads modifies the
* contents of this array, access to the instance must be synchronized externally. Failure to do so
* may lead to an inconsistent state.
* </p>
*/
public class JsonArray extends JsonValue implements Iterable<JsonValue> {
private final List<JsonValue> values;
/**
* Creates a new empty JsonArray.
*/
public JsonArray() {
values = new ArrayList<>();
}
/**
* Creates a new JsonArray with the contents of the specified JSON array.
*
* @param array the JsonArray to get the initial contents from, must not be <code>null</code>
*/
public JsonArray(JsonArray array) {
Objects.requireNonNull(array);
values = new ArrayList<>(array.values);
}
/**
* Appends the JSON representation of the specified <code>int</code> value to the end of this
* array.
*
* @param value the value to add to the array
* @return the array itself, to enable method chaining
*/
public JsonArray add(int value) {
values.add(Json.of(value));
return this;
}
/**
* Appends the JSON representation of the specified <code>long</code> value to the end of this
* array.
*
* @param value the value to add to the array
* @return the array itself, to enable method chaining
*/
public JsonArray add(long value) {
values.add(Json.of(value));
return this;
}
/**
* Appends the JSON representation of the specified <code>float</code> value to the end of this
* array.
*
* @param value the value to add to the array
* @return the array itself, to enable method chaining
*/
public JsonArray add(float value) {
values.add(Json.of(value));
return this;
}
/**
* Appends the JSON representation of the specified <code>double</code> value to the end of this
* array.
*
* @param value the value to add to the array
* @return the array itself, to enable method chaining
*/
public JsonArray add(double value) {
values.add(Json.of(value));
return this;
}
/**
* Appends the JSON representation of the specified <code>boolean</code> value to the end of this
* array.
*
* @param value the value to add to the array
* @return the array itself, to enable method chaining
*/
public JsonArray add(boolean value) {
values.add(Json.of(value));
return this;
}
/**
* Appends the JSON representation of the specified string to the end of this array.
*
* @param value the string to add to the array
* @return the array itself, to enable method chaining
*/
public JsonArray add(String value) {
values.add(Json.of(value));
return this;
}
/**
* Appends the specified JSON value to the end of this array.
*
* @param value the JsonValue to add to the array, must not be <code>null</code>
* @return the array itself, to enable method chaining
*/
public JsonArray add(JsonValue value) {
Objects.requireNonNull(value);
values.add(value);
return this;
}
/**
* Replaces the element at the specified position in this array with the JSON representation of
* the specified <code>int</code> value.
*
* @param index the index of the array element to replace
* @param value the value to be stored at the specified array position
* @return the array itself, to enable method chaining
* @throws IndexOutOfBoundsException if the index is out of range, i.e. <code>index &lt; 0</code> or
* <code>index &gt;= size</code>
*/
public JsonArray set(int index, int value) {
values.set(index, Json.of(value));
return this;
}
/**
* Replaces the element at the specified position in this array with the JSON representation of
* the specified <code>long</code> value.
*
* @param index the index of the array element to replace
* @param value the value to be stored at the specified array position
* @return the array itself, to enable method chaining
* @throws IndexOutOfBoundsException if the index is out of range, i.e. <code>index &lt; 0</code> or
* <code>index &gt;= size</code>
*/
public JsonArray set(int index, long value) {
values.set(index, Json.of(value));
return this;
}
/**
* Replaces the element at the specified position in this array with the JSON representation of
* the specified <code>float</code> value.
*
* @param index the index of the array element to replace
* @param value the value to be stored at the specified array position
* @return the array itself, to enable method chaining
* @throws IndexOutOfBoundsException if the index is out of range, i.e. <code>index &lt; 0</code> or
* <code>index &gt;= size</code>
*/
public JsonArray set(int index, float value) {
values.set(index, Json.of(value));
return this;
}
/**
* Replaces the element at the specified position in this array with the JSON representation of
* the specified <code>double</code> value.
*
* @param index the index of the array element to replace
* @param value the value to be stored at the specified array position
* @return the array itself, to enable method chaining
* @throws IndexOutOfBoundsException if the index is out of range, i.e. <code>index &lt; 0</code> or
* <code>index &gt;= size</code>
*/
public JsonArray set(int index, double value) {
values.set(index, Json.of(value));
return this;
}
/**
* Replaces the element at the specified position in this array with the JSON representation of
* the specified <code>boolean</code> value.
*
* @param index the index of the array element to replace
* @param value the value to be stored at the specified array position
* @return the array itself, to enable method chaining
* @throws IndexOutOfBoundsException if the index is out of range, i.e. <code>index &lt; 0</code> or
* <code>index &gt;= size</code>
*/
public JsonArray set(int index, boolean value) {
values.set(index, Json.of(value));
return this;
}
/**
* Replaces the element at the specified position in this array with the JSON representation of
* the specified string.
*
* @param index the index of the array element to replace
* @param value the string to be stored at the specified array position
* @return the array itself, to enable method chaining
* @throws IndexOutOfBoundsException if the index is out of range, i.e. <code>index &lt; 0</code> or
* <code>index &gt;= size</code>
*/
public JsonArray set(int index, String value) {
values.set(index, Json.of(value));
return this;
}
/**
* Replaces the element at the specified position in this array with the specified JSON value.
*
* @param index the index of the array element to replace
* @param value the value to be stored at the specified array position, must not be <code>null</code>
* @return the array itself, to enable method chaining
* @throws IndexOutOfBoundsException if the index is out of range, i.e. <code>index &lt; 0</code> or
* <code>index &gt;= size</code>
*/
public JsonArray set(int index, JsonValue value) {
if (value == null) {
throw new NullPointerException();
}
values.set(index, value);
return this;
}
/**
* Removes the element at the specified index from this array.
*
* @param index the index of the element to remove
* @return the array itself, to enable method chaining
* @throws IndexOutOfBoundsException if the index is out of range, i.e. <code>index &lt; 0</code> or
* <code>index &gt;= size</code>
*/
public JsonArray remove(int index) {
values.remove(index);
return this;
}
/**
* Returns the number of elements in this array.
*
* @return the number of elements in this array
*/
public int size() {
return values.size();
}
/**
* Returns <code>true</code> if this array contains no elements.
*
* @return <code>true</code> if this array contains no elements
*/
public boolean isEmpty() {
return values.isEmpty();
}
/**
* Returns the value of the element at the specified position in this array.
*
* @param index the index of the array element to return
* @return the value of the element at the specified position
* @throws IndexOutOfBoundsException if the index is out of range, i.e. <code>index &lt; 0</code> or
* <code>index &gt;= size</code>
*/
public JsonValue get(int index) {
return values.get(index);
}
/**
* Returns a list of the values in this array in document order.
*
* @return a list of the values in this array
*/
public List<JsonValue> values() {
return values;
}
/**
* Returns an iterator over the values of this array in document order. The returned iterator
* cannot be used to modify this array.
*
* @return an iterator over the values of this array
*/
@Override
public Iterator<JsonValue> iterator() {
final Iterator<JsonValue> iterator = values.iterator();
return new Iterator<>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public JsonValue next() {
return iterator.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
void write(JsonWriter writer) throws IOException {
writer.writeArrayOpen();
Iterator<JsonValue> iterator = iterator();
if (iterator.hasNext()) {
iterator.next().write(writer);
while (iterator.hasNext()) {
writer.writeArraySeparator();
iterator.next().write(writer);
}
}
writer.writeArrayClose();
}
@Override
public boolean isArray() {
return true;
}
@Override
public JsonArray asArray() {
return this;
}
@Override
public int hashCode() {
return values.hashCode();
}
/**
* Indicates whether a given object is "equal to" this JsonArray. An object is considered equal
* if it is also a <code>JsonArray</code> and both arrays contain the same list of values.
* <p>
* If two JsonArrays are equal, they will also produce the same JSON output.
* </p>
*
* @param object the object to be compared with this JsonArray
* @return {@code true} if the specified object is equal to this JsonArray, <code>false</code>
* otherwise
*/
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null) {
return false;
}
if (getClass() != object.getClass()) {
return false;
}
JsonArray other = (JsonArray) object;
return values.equals(other.values);
}
}

View file

@ -0,0 +1,261 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import java.io.IOException;
import java.time.Instant;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
public class JsonBuilder {
private final Appendable appendable;
private State state;
protected JsonBuilder() {
this(new StringBuilder());
}
protected JsonBuilder(Appendable appendable) {
this.appendable = appendable;
this.state = new State(null, 0, Structure.DOCSTART, true);
}
public JsonBuilder beginCollection() throws IOException {
this.state = new State(state, state.level + 1, Structure.COLLECTION, true);
appendable.append('[');
return this;
}
public JsonBuilder endCollection() throws IOException {
if (state.structure != Structure.COLLECTION) {
throw new IOException("no array to close");
}
appendable.append(']');
this.state = state != null ? state.parent : null;
return this;
}
public JsonBuilder beginMap() throws IOException {
if (state.structure == Structure.COLLECTION) {
beginArrayValue();
}
this.state = new State(state, state.level + 1, Structure.MAP, true);
appendable.append('{');
return this;
}
public JsonBuilder endMap() throws IOException {
if (state.structure != Structure.MAP && state.structure != Structure.KEY) {
throw new IOException("no object to close");
}
appendable.append('}');
this.state = state != null ? state.parent : null;
return this;
}
public JsonBuilder buildMap(Map<String, Object> map) throws IOException {
Objects.requireNonNull(map);
boolean wrap = state.structure != Structure.MAP;
if (wrap) {
beginMap();
}
for (Map.Entry<String, Object> entry : map.entrySet()) {
buildKey(entry.getKey());
buildValue(entry.getValue());
}
if (wrap) {
endMap();
}
return this;
}
public JsonBuilder buildCollection(Collection<?> collection) throws IOException {
Objects.requireNonNull(collection);
beginCollection();
for (Object object : collection) {
buildValue(object);
}
endCollection();
return this;
}
@SuppressWarnings("unchecked")
public JsonBuilder buildValue(Object object) throws IOException {
if (object instanceof Map) {
buildMap((Map<String, Object>) object);
return this;
} else if (object instanceof Collection) {
buildCollection((Collection<Object>) object);
return this;
}
if (state.structure == Structure.COLLECTION) {
beginArrayValue();
}
if (object == null) {
buildNull();
} else if (object instanceof CharSequence) {
buildString((CharSequence) object, true);
} else if (object instanceof Boolean) {
buildBoolean((Boolean) object);
} else if (object instanceof Byte) {
buildNumber((byte) object);
} else if (object instanceof Integer) {
buildNumber((int) object);
} else if (object instanceof Long) {
buildNumber((long) object);
} else if (object instanceof Float) {
buildNumber((float) object);
} else if (object instanceof Double) {
buildNumber((double) object);
} else if (object instanceof Number) {
buildNumber((Number) object);
} else if (object instanceof Instant) {
buildInstant((Instant) object);
} else {
throw new IllegalArgumentException("unable to write object class " + object.getClass());
}
return this;
}
public JsonBuilder buildKey(CharSequence string) throws IOException {
if (state.structure == Structure.COLLECTION) {
beginArrayValue();
} else if (state.structure == Structure.MAP || state.structure == Structure.KEY) {
beginKey(string != null ? string.toString() : null);
}
buildString(string, true);
if (state.structure == Structure.MAP || state.structure == Structure.KEY) {
endKey(string != null ? string.toString() : null);
}
state.structure = Structure.KEY;
return this;
}
public JsonBuilder buildNull() throws IOException {
buildString("null", false);
return this;
}
public String build() {
return appendable.toString();
}
private void beginKey(String k) throws IOException {
if (state.first) {
state.first = false;
} else {
appendable.append(",");
}
}
private void endKey(String k) throws IOException {
appendable.append(":");
}
private void beginArrayValue() throws IOException {
if (state.first) {
state.first = false;
} else {
appendable.append(",");
}
}
private void buildBoolean(boolean bool) throws IOException {
buildString(bool ? "true" : "false", false);
}
private void buildNumber(Number number) throws IOException {
buildString(number != null ? number.toString() : null, false);
}
private void buildInstant(Instant instant) throws IOException {
buildString(instant.toString(), true);
}
private void buildString(CharSequence string, boolean escape) throws IOException {
appendable.append(escape ? escapeString(string) : string);
}
private CharSequence escapeString(CharSequence string) {
StringBuilder sb = new StringBuilder();
sb.append('"');
int start = 0;
int l = string.length();
for (int i = 0; i < l; i++) {
char c = string.charAt(i);
// In JavaScript, U+2028 and U+2029 characters count as line endings and must be encoded.
// http://stackoverflow.com/questions/2965293/javascript-parse-error-on-u2028-unicode-character
if (c == '"' || c == '\\' || c < 32 || c == '\u2028' || c == '\u2029') {
if (i > start) {
sb.append(string, start, i);
}
start = i + 1;
sb.append(escapeCharacter(c));
}
}
if (l > start) {
sb.append(string, start, l);
}
sb.append('"');
return sb;
}
private static String escapeCharacter(char c) {
switch (c) {
case '\n' -> {
return "\\n";
}
case '\r' -> {
return "\\r";
}
case '\t' -> {
return "\\t";
}
case '\\' -> {
return "\\\\";
}
case '\'' -> {
return "\\'";
}
case '\"' -> {
return "\\\"";
}
}
String hex = Integer.toHexString(c);
return "\\u0000".substring(0, 6 - hex.length()) + hex;
}
private enum Structure {
DOCSTART, MAP, KEY, COLLECTION
}
private static class State {
State parent;
int level;
Structure structure;
boolean first;
State(State parent, int level, Structure structure, boolean first) {
this.parent = parent;
this.level = level;
this.structure = structure;
this.first = first;
}
}
}

View file

@ -1,110 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
public class JsonDefaultHandler implements JsonHandler<JsonArray, JsonObject> {
protected JsonValue value;
public JsonDefaultHandler() {
}
public JsonValue getValue() {
return value;
}
@Override
public JsonArray startArray() {
return new JsonArray();
}
@Override
public JsonObject startObject() {
return new JsonObject();
}
@Override
public void startNull() {
}
@Override
public void endNull() {
value = JsonLiteral.NULL;
}
@Override
public void startBoolean() {
}
@Override
public void endBoolean(boolean bool) {
value = bool ? JsonLiteral.TRUE : JsonLiteral.FALSE;
}
@Override
public void startString() {
}
@Override
public void endString(String string) {
value = new JsonString(string);
}
@Override
public void startNumber() {
}
@Override
public void endNumber(String string) {
value = new JsonNumber(string);
}
@Override
public void endArray(JsonArray array) {
value = array;
}
@Override
public void startArrayValue(JsonArray array) {
}
@Override
public void endObject(JsonObject object) {
value = object;
}
@Override
public void startObjectName(JsonObject object) {
}
@Override
public void endObjectName(JsonObject object, String name) {
}
@Override
public void startObjectValue(JsonObject object, String name) {
}
@Override
public void endArrayValue(JsonArray array) {
array.add(value);
}
@Override
public void endObjectValue(JsonObject object, String name) {
object.add(name, value);
}
}

View file

@ -1,28 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
@SuppressWarnings("serial")
public class JsonException extends RuntimeException {
JsonException(Throwable throwable) {
super(throwable);
}
JsonException(String message) {
super(message);
}
}

View file

@ -1,196 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
/**
* An interface for parser events. A {@link JsonHandler} can be given to a {@link JsonReader}. The
* parser will then call the methods of the given handler while reading the input.
* <p>
* Implementations that build an object representation of the parsed JSON can return arbitrary handler
* objects for JSON arrays and JSON objects in {@link #startArray()} and {@link #startObject()}.
* These handler objects will then be provided in all subsequent parser events for this particular
* array or object. They can be used to keep track the elements of a JSON array or object.
* </p>
*
* @param <A> The type of handlers used for JSON arrays
* @param <O> The type of handlers used for JSON objects
* @see JsonReader
*/
public interface JsonHandler<A, O> {
/**
* Indicates the beginning of a <code>null</code> literal in the JSON input. This method will be
* called when reading the first character of the literal.
*/
void startNull();
/**
* Indicates the end of a <code>null</code> literal in the JSON input. This method will be called
* after reading the last character of the literal.
*/
void endNull();
/**
* Indicates the beginning of a boolean literal (<code>true</code> or <code>false</code>) in the
* JSON input. This method will be called when reading the first character of the literal.
*/
void startBoolean();
/**
* Indicates the end of a boolean literal (<code>true</code> or <code>false</code>) in the JSON
* input. This method will be called after reading the last character of the literal.
*
* @param value the parsed boolean value
*/
void endBoolean(boolean value);
/**
* Indicates the beginning of a string in the JSON input. This method will be called when reading
* the opening double quote character (<code>'&quot;'</code>).
*/
void startString();
/**
* Indicates the end of a string in the JSON input. This method will be called after reading the
* closing double quote character (<code>'&quot;'</code>).
*
* @param string the parsed string
*/
void endString(String string);
/**
* Indicates the beginning of a number in the JSON input. This method will be called when reading
* the first character of the number.
*/
void startNumber();
/**
* Indicates the end of a number in the JSON input. This method will be called after reading the
* last character of the number.
*
* @param string the parsed number string
*/
void endNumber(String string);
/**
* Indicates the beginning of an array in the JSON input. This method will be called when reading
* the opening square bracket character (<code>'['</code>).
* <p>
* This method may return an object to handle subsequent parser events for this array. This array
* handler will then be provided in all calls to {@link #startArrayValue(Object)
* startArrayValue()}, {@link #endArrayValue(Object) endArrayValue()}, and
* {@link #endArray(Object) endArray()} for this array.
* </p>
*
* @return a handler for this array, or <code>null</code> if not needed
*/
A startArray();
/**
* Indicates the end of an array in the JSON input. This method will be called after reading the
* closing square bracket character (<code>']'</code>).
*
* @param array the array handler returned from {@link #startArray()}, or <code>null</code> if not
* provided
*/
void endArray(A array);
/**
* Indicates the beginning of an array element in the JSON input. This method will be called when
* reading the first character of the element, just before the call to the <code>start</code>
* method for the specific element type ({@link #startString()}, {@link #startNumber()}, etc.).
*
* @param array the array handler returned from {@link #startArray()}, or <code>null</code> if not
* provided
*/
void startArrayValue(A array);
/**
* Indicates the end of an array element in the JSON input. This method will be called after
* reading the last character of the element value, just after the <code>end</code> method for the
* specific element type (like {@link #endString(String) endString()}, {@link #endNumber(String)
* endNumber()}, etc.).
*
* @param array the array handler returned from {@link #startArray()}, or <code>null</code> if not
* provided
*/
void endArrayValue(A array);
/**
* Indicates the beginning of an object in the JSON input. This method will be called when reading
* the opening curly bracket character (<code>'{'</code>).
* <p>
* This method may return an object to handle subsequent parser events for this object. This
* object handler will be provided in all calls to {@link #startObjectName(Object)
* startObjectName()}, {@link #endObjectName(Object, String) endObjectName()},
* {@link #startObjectValue(Object, String) startObjectValue()},
* {@link #endObjectValue(Object, String) endObjectValue()}, and {@link #endObject(Object)
* endObject()} for this object.
* </p>
*
* @return a handler for this object, or <code>null</code> if not needed
*/
O startObject();
/**
* Indicates the end of an object in the JSON input. This method will be called after reading the
* closing curly bracket character (<code>'}'</code>).
*
* @param object the object handler returned from {@link #startObject()}, or null if not provided
*/
void endObject(O object);
/**
* Indicates the beginning of the name of an object member in the JSON input. This method will be
* called when reading the opening quote character ('&quot;') of the member name.
*
* @param object the object handler returned from {@link #startObject()}, or <code>null</code> if not
* provided
*/
void startObjectName(O object);
/**
* Indicates the end of an object member name in the JSON input. This method will be called after
* reading the closing quote character (<code>'"'</code>) of the member name.
*
* @param object the object handler returned from {@link #startObject()}, or null if not provided
* @param name the parsed member name
*/
void endObjectName(O object, String name);
/**
* Indicates the beginning of the name of an object member in the JSON input. This method will be
* called when reading the opening quote character ('&quot;') of the member name.
*
* @param object the object handler returned from {@link #startObject()}, or <code>null</code> if not
* provided
* @param name the member name
*/
void startObjectValue(O object, String name);
/**
* Indicates the end of an object member value in the JSON input. This method will be called after
* reading the last character of the member value, just after the <code>end</code> method for the
* specific member type (like {@link #endString(String) endString()}, {@link #endNumber(String)
* endNumber()}, etc.).
*
* @param object the object handler returned from {@link #startObject()}, or null if not provided
* @param name the parsed member name
*/
void endObjectValue(O object, String name);
}

View file

@ -1,91 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import java.io.IOException;
/**
* A JSON literal.
*/
class JsonLiteral extends JsonValue {
public static final JsonLiteral NULL = new JsonLiteral("null");
public static final JsonLiteral TRUE = new JsonLiteral("true");
public static final JsonLiteral FALSE = new JsonLiteral("false");
private final String value;
private final boolean isNull;
private final boolean isTrue;
private final boolean isFalse;
JsonLiteral(String value) {
this.value = value;
isNull = "null".equals(value);
isTrue = "true".equals(value);
isFalse = "false".equals(value);
}
@Override
void write(JsonWriter writer) throws IOException {
writer.writeLiteral(value);
}
@Override
public String toString() {
return value;
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public boolean isNull() {
return isNull;
}
@Override
public boolean isTrue() {
return isTrue;
}
@Override
public boolean isFalse() {
return isFalse;
}
@Override
public boolean isBoolean() {
return isTrue || isFalse;
}
@Override
public boolean asBoolean() {
return isNull ? super.asBoolean() : isTrue;
}
@Override
public boolean equals(Object object) {
return this == object || object != null && getClass() == object.getClass() && value.equals(((JsonLiteral) object).value);
}
}

View file

@ -1,108 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JsonMapper {
private JsonMapper() {
// do not instantiate this class
}
public static Object asObject(JsonValue value) {
if (value.isBoolean()) {
return value.asBoolean();
} else if (value.isInt()) {
return value.asInt();
} else if (value.isLong()) {
return value.asLong();
} else if (value.isFloat()) {
return value.asFloat();
} else if (value.isDouble()) {
return value.asDouble();
} else if (value.isString()) {
return value.asString();
} else if (value.isArray()) {
return asList(value.asArray());
} else if (value.isObject()) {
return asMap(value.asObject());
} else {
return null;
}
}
public static List<Object> asList(JsonArray array) {
List<Object> list = new ArrayList<>(array.size());
for (JsonValue element : array) {
list.add(asObject(element));
}
return list;
}
public static Map<String, Object> asMap(JsonObject object) {
Map<String, Object> map = new HashMap<>(object.size(), 1.f);
for (JsonObject.Member member : object) {
map.put(member.getName(), asObject(member.getValue()));
}
return map;
}
public static JsonValue asJsonValue(Object object) {
if (object == null) {
return JsonLiteral.NULL;
} else if (object instanceof Boolean) {
return Json.of((Boolean) object);
} else if (object instanceof Integer) {
return Json.of((Integer) object);
} else if (object instanceof Long) {
return Json.of((Long) object);
} else if (object instanceof Float) {
return Json.of((Float) object);
} else if (object instanceof Double) {
return Json.of((Double) object);
} else if (object instanceof String) {
return Json.of((String) object);
} else if (object instanceof Collection) {
return asJsonArray((Collection<?>) object);
} else if (object instanceof Map) {
return asJsonObject((Map<?, ?>) object);
} else {
return null;
}
}
public static JsonArray asJsonArray(Collection<?> collection) {
JsonArray array = new JsonArray();
for (Object element : collection) {
array.add(asJsonValue(element));
}
return array;
}
public static JsonObject asJsonObject(Map<?, ?> map) {
JsonObject object = new JsonObject();
for (Map.Entry<?, ?> entry : map.entrySet()) {
object.add(String.valueOf(entry.getKey()),
asJsonValue(entry.getValue()));
}
return object;
}
}

View file

@ -1,114 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import java.io.IOException;
import java.util.Objects;
/**
*
*/
class JsonNumber extends JsonValue {
private final String string;
JsonNumber(String string) {
Objects.requireNonNull(string);
this.string = string;
}
@Override
public String toString() {
return string;
}
@Override
void write(JsonWriter writer) throws IOException {
writer.writeNumber(string);
}
@Override
public boolean isInt() {
try {
asInt();
} catch (NumberFormatException e) {
return false;
}
return true;
}
@Override
public boolean isLong() {
try {
asLong();
} catch (NumberFormatException e) {
return false;
}
return true;
}
@Override
public boolean isFloat() {
try {
asFloat();
} catch (NumberFormatException e) {
return false;
}
return true;
}
@Override
public boolean isDouble() {
try {
asDouble();
} catch (NumberFormatException e) {
return false;
}
return true;
}
@Override
public int asInt() {
return Integer.parseInt(string, 10);
}
@Override
public long asLong() {
return Long.parseLong(string, 10);
}
@Override
public float asFloat() {
return Float.parseFloat(string);
}
@Override
public double asDouble() {
return Double.parseDouble(string);
}
@Override
public int hashCode() {
return string.hashCode();
}
@Override
public boolean equals(Object object) {
return this == object || object != null && getClass() == object.getClass()
&& string.equals(((JsonNumber) object).string);
}
}

View file

@ -1,785 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
/**
* Represents a JSON object, a set of name/value pairs, where the names are strings and the values
* are JSON values.
* <p>
* Members can be added using the <code>add(String, ...)</code> methods which accept instances of
* {@link JsonValue}, strings, primitive numbers, and boolean values. To modify certain values of an
* object, use the <code>set(String, ...)</code> methods. Please note that the <code>add</code>
* methods are faster than <code>set</code> as they do not search for existing members. On the other
* hand, the <code>add</code> methods do not prevent adding multiple members with the same name.
* Duplicate names are discouraged but not prohibited by JSON.
* </p>
* <p>
* Members can be accessed by their name using {@link #get(String)}. A list of all names can be
* obtained from the method {@link #names()}. This class also supports iterating over the members in
* document order using an {@link #iterator()} or an enhanced for loop:
* </p>
* <pre>
* for (Member member : jsonObject) {
* String name = member.getName();
* JsonValue value = member.getValue();
* ...
* }
* </pre>
* <p>
* Even though JSON objects are unordered by definition, instances of this class preserve the order
* of members to allow processing in document order and to guarantee a predictable output.
* </p>
* <p>
* Note that this class is <strong>not thread-safe</strong>. If multiple threads access a
* <code>JsonObject</code> instance concurrently, while at least one of these threads modifies the
* contents of this object, access to the instance must be synchronized externally. Failure to do so
* may lead to an inconsistent state.
* </p>
*/
public class JsonObject extends JsonValue implements Iterable<JsonObject.Member> {
private final List<String> names;
private final List<JsonValue> values;
private HashIndexTable table;
/**
* Creates a new empty JsonObject.
*/
public JsonObject() {
names = new ArrayList<>();
values = new ArrayList<>();
table = new HashIndexTable();
}
/**
* Creates a new JsonObject, initialized with the contents of the specified JSON object.
*
* @param object the JSON object to get the initial contents from, must not be <code>null</code>
*/
public JsonObject(JsonObject object) {
Objects.requireNonNull(object);
names = new ArrayList<>(object.names);
values = new ArrayList<>(object.values);
table = new HashIndexTable();
updateHashIndex();
}
/**
* Appends a new member to the end of this object, with the specified name and the JSON
* representation of the specified <code>int</code> value.
* <p>
* This method <strong>does not prevent duplicate names</strong>. Calling this method with a name
* that already exists in the object will append another member with the same name. In order to
* replace existing members, use the method <code>set(name, value)</code> instead. However,
* <strong> <em>add</em> is much faster than <em>set</em></strong> (because it does not need to
* search for existing members). Therefore <em>add</em> should be preferred when constructing new
* objects.
* </p>
*
* @param name the name of the member to add
* @param value the value of the member to add
* @return the object itself, to enable method chaining
*/
public JsonObject add(String name, int value) {
add(name, Json.of(value));
return this;
}
/**
* Appends a new member to the end of this object, with the specified name and the JSON
* representation of the specified <code>long</code> value.
* <p>
* This method <strong>does not prevent duplicate names</strong>. Calling this method with a name
* that already exists in the object will append another member with the same name. In order to
* replace existing members, use the method <code>set(name, value)</code> instead. However,
* <strong> <em>add</em> is much faster than <em>set</em></strong> (because it does not need to
* search for existing members). Therefore <em>add</em> should be preferred when constructing new
* objects.
* </p>
*
* @param name the name of the member to add
* @param value the value of the member to add
* @return the object itself, to enable method chaining
*/
public JsonObject add(String name, long value) {
add(name, Json.of(value));
return this;
}
/**
* Appends a new member to the end of this object, with the specified name and the JSON
* representation of the specified <code>float</code> value.
* <p>
* This method <strong>does not prevent duplicate names</strong>. Calling this method with a name
* that already exists in the object will append another member with the same name. In order to
* replace existing members, use the method <code>set(name, value)</code> instead. However,
* <strong> <em>add</em> is much faster than <em>set</em></strong> (because it does not need to
* search for existing members). Therefore <em>add</em> should be preferred when constructing new
* objects.
* </p>
*
* @param name the name of the member to add
* @param value the value of the member to add
* @return the object itself, to enable method chaining
*/
public JsonObject add(String name, float value) {
add(name, Json.of(value));
return this;
}
/**
* Appends a new member to the end of this object, with the specified name and the JSON
* representation of the specified <code>double</code> value.
* <p>
* This method <strong>does not prevent duplicate names</strong>. Calling this method with a name
* that already exists in the object will append another member with the same name. In order to
* replace existing members, use the method <code>set(name, value)</code> instead. However,
* <strong> <em>add</em> is much faster than <em>set</em></strong> (because it does not need to
* search for existing members). Therefore <em>add</em> should be preferred when constructing new
* objects.
* </p>
*
* @param name the name of the member to add
* @param value the value of the member to add
* @return the object itself, to enable method chaining
*/
public JsonObject add(String name, double value) {
add(name, Json.of(value));
return this;
}
/**
* Appends a new member to the end of this object, with the specified name and the JSON
* representation of the specified <code>boolean</code> value.
* <p>
* This method <strong>does not prevent duplicate names</strong>. Calling this method with a name
* that already exists in the object will append another member with the same name. In order to
* replace existing members, use the method <code>set(name, value)</code> instead. However,
* <strong> <em>add</em> is much faster than <em>set</em></strong> (because it does not need to
* search for existing members). Therefore <em>add</em> should be preferred when constructing new
* objects.
* </p>
*
* @param name the name of the member to add
* @param value the value of the member to add
* @return the object itself, to enable method chaining
*/
public JsonObject add(String name, boolean value) {
add(name, Json.of(value));
return this;
}
/**
* Appends a new member to the end of this object, with the specified name and the JSON
* representation of the specified string.
* <p>
* This method <strong>does not prevent duplicate names</strong>. Calling this method with a name
* that already exists in the object will append another member with the same name. In order to
* replace existing members, use the method <code>set(name, value)</code> instead. However,
* <strong> <em>add</em> is much faster than <em>set</em></strong> (because it does not need to
* search for existing members). Therefore <em>add</em> should be preferred when constructing new
* objects.
* </p>
*
* @param name the name of the member to add
* @param value the value of the member to add
* @return the object itself, to enable method chaining
*/
public JsonObject add(String name, String value) {
add(name, Json.of(value));
return this;
}
/**
* Appends a new member to the end of this object, with the specified name and the specified JSON
* value.
* <p>
* This method <strong>does not prevent duplicate names</strong>. Calling this method with a name
* that already exists in the object will append another member with the same name. In order to
* replace existing members, use the method <code>set(name, value)</code> instead. However,
* <strong> <em>add</em> is much faster than <em>set</em></strong> (because it does not need to
* search for existing members). Therefore <em>add</em> should be preferred when constructing new
* objects.
* </p>
*
* @param name the name of the member to add
* @param value the value of the member to add, must not be <code>null</code>
* @return the object itself, to enable method chaining
*/
public JsonObject add(String name, JsonValue value) {
if (name == null) {
throw new NullPointerException("name is null");
}
if (value == null) {
throw new NullPointerException("value is null");
}
table.add(name, names.size());
names.add(name);
values.add(value);
return this;
}
/**
* Sets the value of the member with the specified name to the JSON representation of the
* specified <code>int</code> value. If this object does not contain a member with this name, a
* new member is added at the end of the object. If this object contains multiple members with
* this name, only the last one is changed.
* <p>
* This method should <strong>only be used to modify existing objects</strong>. To fill a new
* object with members, the method <code>add(name, value)</code> should be preferred which is much
* faster (as it does not need to search for existing members).
* </p>
*
* @param name the name of the member to replace
* @param value the value to set to the member
* @return the object itself, to enable method chaining
*/
public JsonObject set(String name, int value) {
set(name, Json.of(value));
return this;
}
/**
* Sets the value of the member with the specified name to the JSON representation of the
* specified <code>long</code> value. If this object does not contain a member with this name, a
* new member is added at the end of the object. If this object contains multiple members with
* this name, only the last one is changed.
* <p>
* This method should <strong>only be used to modify existing objects</strong>. To fill a new
* object with members, the method <code>add(name, value)</code> should be preferred which is much
* faster (as it does not need to search for existing members).
* </p>
*
* @param name the name of the member to replace
* @param value the value to set to the member
* @return the object itself, to enable method chaining
*/
public JsonObject set(String name, long value) {
set(name, Json.of(value));
return this;
}
/**
* Sets the value of the member with the specified name to the JSON representation of the
* specified <code>float</code> value. If this object does not contain a member with this name, a
* new member is added at the end of the object. If this object contains multiple members with
* this name, only the last one is changed.
* <p>
* This method should <strong>only be used to modify existing objects</strong>. To fill a new
* object with members, the method <code>add(name, value)</code> should be preferred which is much
* faster (as it does not need to search for existing members).
* </p>
*
* @param name the name of the member to add
* @param value the value of the member to add
* @return the object itself, to enable method chaining
*/
public JsonObject set(String name, float value) {
set(name, Json.of(value));
return this;
}
/**
* Sets the value of the member with the specified name to the JSON representation of the
* specified <code>double</code> value. If this object does not contain a member with this name, a
* new member is added at the end of the object. If this object contains multiple members with
* this name, only the last one is changed.
* <p>
* This method should <strong>only be used to modify existing objects</strong>. To fill a new
* object with members, the method <code>add(name, value)</code> should be preferred which is much
* faster (as it does not need to search for existing members).
* </p>
*
* @param name the name of the member to add
* @param value the value of the member to add
* @return the object itself, to enable method chaining
*/
public JsonObject set(String name, double value) {
set(name, Json.of(value));
return this;
}
/**
* Sets the value of the member with the specified name to the JSON representation of the
* specified <code>boolean</code> value. If this object does not contain a member with this name,
* a new member is added at the end of the object. If this object contains multiple members with
* this name, only the last one is changed.
* <p>
* This method should <strong>only be used to modify existing objects</strong>. To fill a new
* object with members, the method <code>add(name, value)</code> should be preferred which is much
* faster (as it does not need to search for existing members).
* </p>
*
* @param name the name of the member to add
* @param value the value of the member to add
* @return the object itself, to enable method chaining
*/
public JsonObject set(String name, boolean value) {
set(name, Json.of(value));
return this;
}
/**
* Sets the value of the member with the specified name to the JSON representation of the
* specified string. If this object does not contain a member with this name, a new member is
* added at the end of the object. If this object contains multiple members with this name, only
* the last one is changed.
* <p>
* This method should <strong>only be used to modify existing objects</strong>. To fill a new
* object with members, the method <code>add(name, value)</code> should be preferred which is much
* faster (as it does not need to search for existing members).
* </p>
*
* @param name the name of the member to add
* @param value the value of the member to add
* @return the object itself, to enable method chaining
*/
public JsonObject set(String name, String value) {
set(name, Json.of(value));
return this;
}
/**
* Sets the value of the member with the specified name to the specified JSON value. If this
* object does not contain a member with this name, a new member is added at the end of the
* object. If this object contains multiple members with this name, only the last one is changed.
* <p>
* This method should <strong>only be used to modify existing objects</strong>. To fill a new
* object with members, the method <code>add(name, value)</code> should be preferred which is much
* faster (as it does not need to search for existing members).
* </p>
*
* @param name the name of the member to add
* @param value the value of the member to add, must not be <code>null</code>
* @return the object itself, to enable method chaining
*/
public JsonObject set(String name, JsonValue value) {
if (name == null) {
throw new NullPointerException("name is null");
}
if (value == null) {
throw new NullPointerException("value is null");
}
int index = indexOf(name);
if (index != -1) {
values.set(index, value);
} else {
table.add(name, names.size());
names.add(name);
values.add(value);
}
return this;
}
/**
* Removes a member with the specified name from this object. If this object contains multiple
* members with the given name, only the last one is removed. If this object does not contain a
* member with the specified name, the object is not modified.
*
* @param name the name of the member to remove
* @return the object itself, to enable method chaining
*/
public JsonObject remove(String name) {
Objects.requireNonNull(name);
int index = indexOf(name);
if (index != -1) {
table.remove(index);
names.remove(index);
values.remove(index);
}
return this;
}
/**
* Copies all members of the specified object into this object. When the specified object contains
* members with names that also exist in this object, the existing values in this object will be
* replaced by the corresponding values in the specified object.
*
* @param object the object to merge
* @return the object itself, to enable method chaining
*/
public JsonObject merge(JsonObject object) {
Objects.requireNonNull(object);
for (Member member : object) {
this.set(member.name, member.value);
}
return this;
}
/**
* Returns the value of the member with the specified name in this object. If this object contains
* multiple members with the given name, this method will return the last one.
*
* @param name the name of the member whose value is to be returned
* @return the value of the last member with the specified name, or <code>null</code> if this
* object does not contain a member with that name
*/
public JsonValue get(String name) {
Objects.requireNonNull(name);
int index = indexOf(name);
return index != -1 ? values.get(index) : null;
}
/**
* Returns the <code>int</code> value of the member with the specified name in this object. If
* this object does not contain a member with this name, the given default value is returned. If
* this object contains multiple members with the given name, the last one will be picked. If this
* member's value does not represent a JSON number or if it cannot be interpreted as Java
* <code>int</code>, an exception is thrown.
*
* @param name the name of the member whose value is to be returned
* @param defaultValue the value to be returned if the requested member is missing
* @return the value of the last member with the specified name, or the given default value if
* this object does not contain a member with that name
*/
public int getInt(String name, int defaultValue) {
JsonValue value = get(name);
return value != null ? value.asInt() : defaultValue;
}
/**
* Returns the <code>long</code> value of the member with the specified name in this object. If
* this object does not contain a member with this name, the given default value is returned. If
* this object contains multiple members with the given name, the last one will be picked. If this
* member's value does not represent a JSON number or if it cannot be interpreted as Java
* <code>long</code>, an exception is thrown.
*
* @param name the name of the member whose value is to be returned
* @param defaultValue the value to be returned if the requested member is missing
* @return the value of the last member with the specified name, or the given default value if
* this object does not contain a member with that name
*/
public long getLong(String name, long defaultValue) {
JsonValue value = get(name);
return value != null ? value.asLong() : defaultValue;
}
/**
* Returns the <code>float</code> value of the member with the specified name in this object. If
* this object does not contain a member with this name, the given default value is returned. If
* this object contains multiple members with the given name, the last one will be picked. If this
* member's value does not represent a JSON number or if it cannot be interpreted as Java
* <code>float</code>, an exception is thrown.
*
* @param name the name of the member whose value is to be returned
* @param defaultValue the value to be returned if the requested member is missing
* @return the value of the last member with the specified name, or the given default value if
* this object does not contain a member with that name
*/
public float getFloat(String name, float defaultValue) {
JsonValue value = get(name);
return value != null ? value.asFloat() : defaultValue;
}
/**
* Returns the <code>double</code> value of the member with the specified name in this object. If
* this object does not contain a member with this name, the given default value is returned. If
* this object contains multiple members with the given name, the last one will be picked. If this
* member's value does not represent a JSON number or if it cannot be interpreted as Java
* <code>double</code>, an exception is thrown.
*
* @param name the name of the member whose value is to be returned
* @param defaultValue the value to be returned if the requested member is missing
* @return the value of the last member with the specified name, or the given default value if
* this object does not contain a member with that name
*/
public double getDouble(String name, double defaultValue) {
JsonValue value = get(name);
return value != null ? value.asDouble() : defaultValue;
}
/**
* Returns the <code>boolean</code> value of the member with the specified name in this object. If
* this object does not contain a member with this name, the given default value is returned. If
* this object contains multiple members with the given name, the last one will be picked. If this
* member's value does not represent a JSON <code>true</code> or <code>false</code> value, an
* exception is thrown.
*
* @param name the name of the member whose value is to be returned
* @param defaultValue the value to be returned if the requested member is missing
* @return the value of the last member with the specified name, or the given default value if
* this object does not contain a member with that name
*/
public boolean getBoolean(String name, boolean defaultValue) {
JsonValue value = get(name);
return value != null ? value.asBoolean() : defaultValue;
}
/**
* Returns the <code>String</code> value of the member with the specified name in this object. If
* this object does not contain a member with this name, the given default value is returned. If
* this object contains multiple members with the given name, the last one is picked. If this
* member's value does not represent a JSON string, an exception is thrown.
*
* @param name the name of the member whose value is to be returned
* @param defaultValue the value to be returned if the requested member is missing
* @return the value of the last member with the specified name, or the given default value if
* this object does not contain a member with that name
*/
public String getString(String name, String defaultValue) {
JsonValue value = get(name);
return value != null ? value.asString() : defaultValue;
}
/**
* Returns the number of members (name/value pairs) in this object.
*
* @return the number of members in this object
*/
public int size() {
return names.size();
}
/**
* Returns <code>true</code> if this object contains no members.
*
* @return <code>true</code> if this object contains no members
*/
public boolean isEmpty() {
return names.isEmpty();
}
/**
* Returns a list of the names in this object in document order. The returned list is backed by
* this object and will reflect subsequent changes. It cannot be used to modify this object.
* Attempts to modify the returned list will result in an exception.
*
* @return a list of the names in this object
*/
public List<String> names() {
return Collections.unmodifiableList(names);
}
/**
* Returns an iterator over the members of this object in document order. The returned iterator
* cannot be used to modify this object.
*
* @return an iterator over the members of this object
*/
@Override
public Iterator<Member> iterator() {
final Iterator<String> namesIterator = names.iterator();
final Iterator<JsonValue> valuesIterator = values.iterator();
return new Iterator<Member>() {
@Override
public boolean hasNext() {
return namesIterator.hasNext();
}
@Override
public Member next() {
String name = namesIterator.next();
JsonValue value = valuesIterator.next();
return new Member(name, value);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
void write(JsonWriter writer) throws IOException {
writer.writeObjectOpen();
Iterator<String> namesIterator = names.iterator();
Iterator<JsonValue> valuesIterator = values.iterator();
if (namesIterator.hasNext()) {
writer.writeMemberName(namesIterator.next());
writer.writeMemberSeparator();
valuesIterator.next().write(writer);
while (namesIterator.hasNext()) {
writer.writeObjectSeparator();
writer.writeMemberName(namesIterator.next());
writer.writeMemberSeparator();
valuesIterator.next().write(writer);
}
}
writer.writeObjectClose();
}
@Override
public boolean isObject() {
return true;
}
@Override
public JsonObject asObject() {
return this;
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + names.hashCode();
result = 31 * result + values.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
JsonObject other = (JsonObject) obj;
return names.equals(other.names) && values.equals(other.values);
}
int indexOf(String name) {
int index = table.get(name);
if (index != -1 && name.equals(names.get(index))) {
return index;
}
return names.lastIndexOf(name);
}
private void updateHashIndex() {
int size = names.size();
for (int i = 0; i < size; i++) {
table.add(names.get(i), i);
}
}
/**
* Represents a member of a JSON object, a pair of a name and a value.
*/
public static class Member {
private final String name;
private final JsonValue value;
public Member(String name, JsonValue value) {
this.name = name;
this.value = value;
}
/**
* Returns the name of this member.
*
* @return the name of this member, never <code>null</code>
*/
public String getName() {
return name;
}
/**
* Returns the value of this member.
*
* @return the value of this member, never <code>null</code>
*/
public JsonValue getValue() {
return value;
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + name.hashCode();
result = 31 * result + value.hashCode();
return result;
}
/**
* Indicates whether a given object is "equal to" this JsonObject. An object is considered equal
* if it is also a <code>JsonObject</code> and both objects contain the same members <em>in
* the same order</em>.
* <p>
* If two JsonObjects are equal, they will also produce the same JSON output.
* </p>
*
* @param object the object to be compared with this JsonObject
* @return <tt>true</tt> if the specified object is equal to this JsonObject, <code>false</code>
* otherwise
*/
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null) {
return false;
}
if (getClass() != object.getClass()) {
return false;
}
Member other = (Member) object;
return name.equals(other.name) && value.equals(other.value);
}
}
/**
*
*/
static class HashIndexTable {
private final byte[] hashTable = new byte[32]; // must be a power of two
HashIndexTable() {
}
HashIndexTable(HashIndexTable original) {
System.arraycopy(original.hashTable, 0, hashTable, 0, hashTable.length);
}
void add(String name, int index) {
int slot = hashSlotFor(name);
if (index < 0xff) {
// increment by 1, 0 stands for empty
hashTable[slot] = (byte) (index + 1);
} else {
hashTable[slot] = 0;
}
}
void remove(int index) {
for (int i = 0; i < hashTable.length; i++) {
if (hashTable[i] == index + 1) {
hashTable[i] = 0;
} else if (hashTable[i] > index + 1) {
hashTable[i]--;
}
}
}
int get(Object name) {
int slot = hashSlotFor(name);
// subtract 1, 0 stands for empty
return (hashTable[slot] & 0xff) - 1;
}
private int hashSlotFor(Object element) {
return element.hashCode() & hashTable.length - 1;
}
}
}

View file

@ -1,460 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import java.io.IOException;
import java.io.Reader;
import java.util.Objects;
/**
* A streaming parser for JSON text. The parser reports all events to a given handler.
*
* @param <A> the JSON array type
* @param <O> the JSON object type
*/
public class JsonReader<A, O> {
private static final int MAX_NESTING_LEVEL = 1000;
private static final int DEFAULT_BUFFER_SIZE = 1024;
private final Reader reader;
private final JsonHandler<A, O> handler;
private char[] buffer;
private int index;
private int fill;
private int current;
private StringBuilder captureBuffer;
private int captureStart;
private int nestingLevel;
/**
* Creates a new JsonParser with the given handler. The parser will report all parser events to
* this handler.
* @param reader the reader
* @param handler the handler to process parser events
*/
public JsonReader(Reader reader, JsonHandler<A, O> handler) {
Objects.requireNonNull(handler);
this.handler = handler;
this.reader = reader;
}
/**
* Reads the entire input from the given reader and parses it as JSON. The input must contain a
* valid JSON value, optionally padded with whitespace.
* <p>
* Characters are read in chunks into a default-sized input buffer. Hence, wrapping a reader in an
* additional <code>BufferedReader</code> likely won't improve reading performance.
* </p>
*
* @throws IOException if an I/O error occurs in the reader
* @throws JsonException if the input is not valid JSON
*/
public void parse() throws IOException {
parse(DEFAULT_BUFFER_SIZE);
}
/**
* Reads the entire input from the given reader and parses it as JSON. The input must contain a
* valid JSON value, optionally padded with whitespace.
* <p>
* Characters are read in chunks into an input buffer of the given size. Hence, wrapping a reader
* in an additional <code>BufferedReader</code> likely won't improve reading performance.
* </p>
*
* @param buffersize the size of the input buffer in chars
* @throws IOException if an I/O error occurs in the reader
* @throws JsonException if the input is not valid JSON
*/
public void parse(int buffersize) throws IOException {
if (reader == null) {
throw new NullPointerException("reader is null");
}
if (buffersize <= 0) {
throw new IllegalArgumentException("buffersize is zero or negative");
}
buffer = new char[buffersize];
index = 0;
fill = 0;
current = 0;
captureStart = -1;
read();
skipWhiteSpace();
readValue();
skipWhiteSpace();
if (!isEndOfText()) {
throw error("Unexpected character");
}
}
private void readValue() throws IOException {
switch (current) {
case 'n':
readNull();
break;
case 't':
readTrue();
break;
case 'f':
readFalse();
break;
case '"':
readString();
break;
case '[':
readArray();
break;
case '{':
readObject();
break;
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
readNumber();
break;
default:
throw expected("value");
}
}
private void readArray() throws IOException {
A array = handler.startArray();
read();
if (++nestingLevel > MAX_NESTING_LEVEL) {
throw error("Nesting too deep");
}
skipWhiteSpace();
if (readChar(']')) {
nestingLevel--;
handler.endArray(array);
return;
}
do {
skipWhiteSpace();
handler.startArrayValue(array);
readValue();
handler.endArrayValue(array);
skipWhiteSpace();
} while (readChar(','));
if (!readChar(']')) {
throw expected("',' or ']'");
}
nestingLevel--;
handler.endArray(array);
}
private void readObject() throws IOException {
O object = handler.startObject();
read();
if (++nestingLevel > MAX_NESTING_LEVEL) {
throw error("Nesting too deep");
}
skipWhiteSpace();
if (readChar('}')) {
nestingLevel--;
handler.endObject(object);
return;
}
do {
skipWhiteSpace();
handler.startObjectName(object);
String name = readName();
handler.endObjectName(object, name);
skipWhiteSpace();
if (!readChar(':')) {
throw expected("':'");
}
skipWhiteSpace();
handler.startObjectValue(object, name);
readValue();
handler.endObjectValue(object, name);
skipWhiteSpace();
} while (readChar(','));
if (!readChar('}')) {
throw expected("',' or '}'");
}
nestingLevel--;
handler.endObject(object);
}
private String readName() throws IOException {
if (current != '"') {
throw expected("name");
}
return readStringInternal();
}
private void readNull() throws IOException {
handler.startNull();
read();
readRequiredChar('u');
readRequiredChar('l');
readRequiredChar('l');
handler.endNull();
}
private void readTrue() throws IOException {
handler.startBoolean();
read();
readRequiredChar('r');
readRequiredChar('u');
readRequiredChar('e');
handler.endBoolean(true);
}
private void readFalse() throws IOException {
handler.startBoolean();
read();
readRequiredChar('a');
readRequiredChar('l');
readRequiredChar('s');
readRequiredChar('e');
handler.endBoolean(false);
}
private void readRequiredChar(char ch) throws IOException {
if (!readChar(ch)) {
throw expected("'" + ch + "'");
}
}
private void readString() throws IOException {
handler.startString();
handler.endString(readStringInternal());
}
private String readStringInternal() throws IOException {
read();
startCapture();
while (current != '"') {
if (current == '\\') {
pauseCapture();
readEscape();
startCapture();
} else if (current < 0x20) {
throw expected("valid string character");
} else {
read();
}
}
String string = endCapture();
read();
return string;
}
private void readEscape() throws IOException {
read();
switch (current) {
case '"':
case '/':
case '\\':
captureBuffer.append((char) current);
break;
case 'b':
captureBuffer.append('\b');
break;
case 'f':
captureBuffer.append('\f');
break;
case 'n':
captureBuffer.append('\n');
break;
case 'r':
captureBuffer.append('\r');
break;
case 't':
captureBuffer.append('\t');
break;
case 'u':
char[] hexChars = new char[4];
for (int i = 0; i < 4; i++) {
read();
if (!isHexDigit()) {
throw expected("hexadecimal digit");
}
hexChars[i] = (char) current;
}
captureBuffer.append((char) Integer.parseInt(new String(hexChars), 16));
break;
default:
throw expected("valid escape sequence");
}
read();
}
private void readNumber() throws IOException {
handler.startNumber();
startCapture();
readChar('-');
int firstDigit = current;
if (!readDigit()) {
throw expected("digit");
}
if (firstDigit != '0') {
while (true) {
if (!readDigit()) {
break;
}
}
}
readFraction();
readExponent();
handler.endNumber(endCapture());
}
private void readFraction() throws IOException {
if (!readChar('.')) {
return;
}
if (!readDigit()) {
throw expected("digit");
}
while (true) {
if (!readDigit()) {
break;
}
}
}
private void readExponent() throws IOException {
if (!readChar('e') && !readChar('E')) {
return;
}
if (!readChar('+')) {
readChar('-');
}
if (!readDigit()) {
throw expected("digit");
}
while (true) {
if (!readDigit()) {
break;
}
}
}
private boolean readChar(char ch) throws IOException {
if (current != ch) {
return false;
}
read();
return true;
}
private boolean readDigit() throws IOException {
if (!isDigit()) {
return false;
}
read();
return true;
}
private void skipWhiteSpace() throws IOException {
while (isWhiteSpace()) {
read();
}
}
private void read() throws IOException {
if (index == fill) {
if (captureStart != -1) {
captureBuffer.append(buffer, captureStart, fill - captureStart);
captureStart = 0;
}
fill = reader.read(buffer, 0, buffer.length);
index = 0;
if (fill == -1) {
current = -1;
index++;
return;
}
}
current = buffer[index++];
}
private void startCapture() {
if (captureBuffer == null) {
captureBuffer = new StringBuilder();
}
captureStart = index - 1;
}
private void pauseCapture() {
int end = current == -1 ? index : index - 1;
captureBuffer.append(buffer, captureStart, end - captureStart);
captureStart = -1;
}
private String endCapture() {
int start = captureStart;
int end = index - 1;
captureStart = -1;
if (captureBuffer.length() > 0) {
captureBuffer.append(buffer, start, end - start);
String captured = captureBuffer.toString();
captureBuffer.setLength(0);
return captured;
}
return new String(buffer, start, end - start);
}
private JsonException expected(String expected) {
if (isEndOfText()) {
return error("Unexpected end of input");
}
return error("Expected " + expected);
}
private JsonException error(String message) {
return new JsonException(message);
}
private boolean isWhiteSpace() {
return current == ' ' || current == '\t' || current == '\n' || current == '\r';
}
private boolean isDigit() {
return current >= '0' && current <= '9';
}
private boolean isHexDigit() {
return current >= '0' && current <= '9'
|| current >= 'a' && current <= 'f'
|| current >= 'A' && current <= 'F';
}
private boolean isEndOfText() {
return current == -1;
}
}

View file

@ -1,59 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import java.io.IOException;
import java.util.Objects;
/**
*
*/
class JsonString extends JsonValue {
private final String string;
JsonString(String string) {
Objects.requireNonNull(string);
this.string = string;
}
@Override
void write(JsonWriter writer) throws IOException {
writer.writeString(string);
}
@Override
public boolean isString() {
return true;
}
@Override
public String asString() {
return string;
}
@Override
public int hashCode() {
return string.hashCode();
}
@Override
public boolean equals(Object object) {
return this == object || object != null && getClass() == object.getClass()
&& string.equals(((JsonString) object).string);
}
}

View file

@ -1,295 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import java.io.IOException;
import java.io.StringWriter;
/**
* Represents a JSON value. This can be a JSON <strong>object</strong>, an <strong> array</strong>,
* a <strong>number</strong>, a <strong>string</strong>, or one of the literals
* <strong>true</strong>, <strong>false</strong>, and <strong>null</strong>.
* <p>
* The literals <strong>true</strong>, <strong>false</strong>, and <strong>null</strong> are
* represented by the constants
* {@code JsonLiteral.NULL}, {@code JsonLiteral.FALSE}, and {@code JsonLiteral.NULL}.
* </p>
* <p>
* JSON <strong>objects</strong> and <strong>arrays</strong> are represented by the subtypes
* {@link JsonObject} and {@link JsonArray}. Instances of these types can be created using the
* public constructors of these classes.
* </p>
* <p>
* Instances that represent JSON <strong>numbers</strong>, <strong>strings</strong> and
* <strong>boolean</strong> values can be created using the static factory methods
* {@code JSON.parse(String)}, {@code valueOf(long)}, {@code valueOf(double)}, etc.
* </p>
* <p>
* In order to find out whether an instance of this class is of a certain type, the methods
* {@link #isObject()}, {@link #isArray()}, {@link #isString()}, {@link #isInt()} etc. can be
* used.
* </p>
* <p>
* If the type of a JSON value is known, the methods {@link #asObject()}, {@link #asArray()},
* {@link #asString()}, {@link #asInt()}, etc. can be used to get this value directly in the
* appropriate target type.
* </p>
*/
public abstract class JsonValue {
public JsonValue() {
}
/**
* Detects whether this value represents a JSON object. If this is the case, this value is an
* instance of {@link JsonObject}.
*
* @return <code>true</code> if this value is an instance of JsonObject
*/
public boolean isObject() {
return false;
}
/**
* Detects whether this value represents a JSON array. If this is the case, this value is an
* instance of {@link JsonArray}.
*
* @return <code>true</code> if this value is an instance of JsonArray
*/
public boolean isArray() {
return false;
}
/**
* Detects whether this value represents a JSON number that is an integer.
*
* @return <code>true</code> if this value represents a JSON number that is an integer
*/
public boolean isInt() {
return false;
}
/**
* Detects whether this value represents a JSON number that is an long.
*
* @return <code>true</code> if this value represents a JSON number that is an long
*/
public boolean isLong() {
return false;
}
/**
* Detects whether this value represents a JSON number that is an float.
*
* @return <code>true</code> if this value represents a JSON number that is an float
*/
public boolean isFloat() {
return false;
}
/**
* Detects whether this value represents a JSON number that is an double.
*
* @return <code>true</code> if this value represents a JSON number that is an double
*/
public boolean isDouble() {
return false;
}
/**
* Detects whether this value represents a JSON string.
*
* @return <code>true</code> if this value represents a JSON string
*/
public boolean isString() {
return false;
}
/**
* Detects whether this value represents a boolean value.
*
* @return <code>true</code> if this value represents either the JSON literal <code>true</code> or
* <code>false</code>
*/
public boolean isBoolean() {
return false;
}
/**
* Detects whether this value represents the JSON literal <code>true</code>.
*
* @return <code>true</code> if this value represents the JSON literal <code>true</code>
*/
public boolean isTrue() {
return false;
}
/**
* Detects whether this value represents the JSON literal <code>false</code>.
*
* @return <code>true</code> if this value represents the JSON literal <code>false</code>
*/
public boolean isFalse() {
return false;
}
/**
* Detects whether this value represents the JSON literal <code>null</code>.
*
* @return <code>true</code> if this value represents the JSON literal <code>null</code>
*/
public boolean isNull() {
return false;
}
/**
* Returns this JSON value as {@link JsonObject}, assuming that this value represents a JSON
* object. If this is not the case, an exception is thrown.
*
* @return a JSONObject for this value
* @throws UnsupportedOperationException if this value is not a JSON object
*/
public JsonObject asObject() {
throw new UnsupportedOperationException("Not an object: " + toString());
}
/**
* Returns this JSON value as {@link JsonArray}, assuming that this value represents a JSON array.
* If this is not the case, an exception is thrown.
*
* @return a JSONArray for this value
* @throws UnsupportedOperationException if this value is not a JSON array
*/
public JsonArray asArray() {
throw new UnsupportedOperationException("Not an array: " + toString());
}
/**
* Returns this JSON value as an <code>int</code> value, assuming that this value represents a
* JSON number that can be interpreted as Java <code>int</code>. If this is not the case, an
* exception is thrown.
* <p>
* To be interpreted as Java <code>int</code>, the JSON number must neither contain an exponent
* nor a fraction part. Moreover, the number must be in the <code>Integer</code> range.
* </p>
*
* @return this value as <code>int</code>
* @throws UnsupportedOperationException if this value is not a JSON number
* @throws NumberFormatException if this JSON number can not be interpreted as <code>int</code> value
*/
public int asInt() {
throw new UnsupportedOperationException("Not a number: " + toString());
}
/**
* Returns this JSON value as a <code>long</code> value, assuming that this value represents a
* JSON number that can be interpreted as Java <code>long</code>. If this is not the case, an
* exception is thrown.
* <p>
* To be interpreted as Java <code>long</code>, the JSON number must neither contain an exponent
* nor a fraction part. Moreover, the number must be in the <code>Long</code> range.
* </p>
*
* @return this value as <code>long</code>
* @throws UnsupportedOperationException if this value is not a JSON number
* @throws NumberFormatException if this JSON number can not be interpreted as <code>long</code> value
*/
public long asLong() {
throw new UnsupportedOperationException();
}
/**
* Returns this JSON value as a <code>float</code> value, assuming that this value represents a
* JSON number. If this is not the case, an exception is thrown.
* <p>
* If the JSON number is out of the <code>Float</code> range, {@link Float#POSITIVE_INFINITY} or
* {@link Float#NEGATIVE_INFINITY} is returned.
* </p>
*
* @return this value as <code>float</code>
* @throws UnsupportedOperationException if this value is not a JSON number
*/
public float asFloat() {
throw new UnsupportedOperationException();
}
/**
* Returns this JSON value as a <code>double</code> value, assuming that this value represents a
* JSON number. If this is not the case, an exception is thrown.
* <p>
* If the JSON number is out of the <code>Double</code> range, {@link Double#POSITIVE_INFINITY} or
* {@link Double#NEGATIVE_INFINITY} is returned.
* </p>
*
* @return this value as <code>double</code>
* @throws UnsupportedOperationException if this value is not a JSON number
*/
public double asDouble() {
throw new UnsupportedOperationException();
}
/**
* Returns this JSON value as String, assuming that this value represents a JSON string. If this
* is not the case, an exception is thrown.
*
* @return the string represented by this value
* @throws UnsupportedOperationException if this value is not a JSON string
*/
public String asString() {
throw new UnsupportedOperationException();
}
/**
* Returns this JSON value as a <code>boolean</code> value, assuming that this value is either
* <code>true</code> or <code>false</code>. If this is not the case, an exception is thrown.
*
* @return this value as <code>boolean</code>
* @throws UnsupportedOperationException if this value is neither <code>true</code> or <code>false</code>
*/
public boolean asBoolean() {
throw new UnsupportedOperationException();
}
/**
* Returns the JSON string for this value in its minimal form, without any additional whitespace.
*
* @return a JSON string that represents this value
*/
@Override
public String toString() {
return toString(JsonWriterConfig.minimal());
}
/**
* Returns the JSON string for this value using the given formatting.
*
* @param config a configuration that controls the formatting or <code>null</code> for the minimal form
* @return a JSON string that represents this value
*/
public String toString(JsonWriterConfig config) {
StringWriter writer = new StringWriter();
try {
write(config.createWriter(writer));
} catch (IOException exception) {
// StringWriter does not throw IOException, so this is impossible
throw new JsonException(exception);
}
return writer.toString();
}
abstract void write(JsonWriter writer) throws IOException;
}

View file

@ -1,140 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import java.io.IOException;
import java.io.Writer;
/**
*
*/
public class JsonWriter {
private static final int CONTROL_CHARACTERS_END = 0x001f;
private static final char[] QUOT_CHARS = {'\\', '"'};
private static final char[] BS_CHARS = {'\\', '\\'};
private static final char[] LF_CHARS = {'\\', 'n'};
private static final char[] CR_CHARS = {'\\', 'r'};
private static final char[] TAB_CHARS = {'\\', 't'};
// In JavaScript, U+2028 and U+2029 characters count as line endings and must be encoded.
// http://stackoverflow.com/questions/2965293/javascript-parse-error-on-u2028-unicode-character
private static final char[] UNICODE_2028_CHARS = {'\\', 'u', '2', '0', '2', '8'};
private static final char[] UNICODE_2029_CHARS = {'\\', 'u', '2', '0', '2', '9'};
private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f'};
protected final Writer writer;
public JsonWriter(Writer writer) {
this.writer = writer;
}
protected void writeLiteral(String value) throws IOException {
writer.write(value);
}
protected void writeNumber(String string) throws IOException {
writer.write(string);
}
protected void writeString(String string) throws IOException {
writer.write('"');
writeJsonString(string);
writer.write('"');
}
protected void writeArrayOpen() throws IOException {
writer.write('[');
}
protected void writeArrayClose() throws IOException {
writer.write(']');
}
protected void writeArraySeparator() throws IOException {
writer.write(',');
}
protected void writeObjectOpen() throws IOException {
writer.write('{');
}
protected void writeObjectClose() throws IOException {
writer.write('}');
}
protected void writeMemberName(String name) throws IOException {
writer.write('"');
writeJsonString(name);
writer.write('"');
}
protected void writeMemberSeparator() throws IOException {
writer.write(':');
}
protected void writeObjectSeparator() throws IOException {
writer.write(',');
}
private void writeJsonString(String string) throws IOException {
int length = string.length();
int start = 0;
for (int index = 0; index < length; index++) {
char[] replacement = getReplacementChars(string.charAt(index));
if (replacement != null) {
writer.write(string, start, index - start);
writer.write(replacement);
start = index + 1;
}
}
writer.write(string, start, length - start);
}
private static char[] getReplacementChars(char ch) {
if (ch > '\\') {
if (ch < '\u2028' || ch > '\u2029') {
// The lower range contains 'a' .. 'z'. Only 2 checks required.
return null;
}
return ch == '\u2028' ? UNICODE_2028_CHARS : UNICODE_2029_CHARS;
}
if (ch == '\\') {
return BS_CHARS;
}
if (ch > '"') {
// This range contains '0' .. '9' and 'A' .. 'Z'. Need 3 checks to get here.
return null;
}
if (ch == '"') {
return QUOT_CHARS;
}
if (ch > CONTROL_CHARACTERS_END) {
return null;
}
if (ch == '\n') {
return LF_CHARS;
}
if (ch == '\r') {
return CR_CHARS;
}
if (ch == '\t') {
return TAB_CHARS;
}
return new char[]{'\\', 'u', '0', '0', HEX_DIGITS[ch >> 4 & 0x000f], HEX_DIGITS[ch & 0x000f]};
}
}

View file

@ -1,157 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
/**
* Controls the formatting of the JSON output. Use one of the available constants.
*/
@FunctionalInterface
public interface JsonWriterConfig {
JsonWriter createWriter(Writer writer);
/**
* Write JSON in its minimal form, without any additional whitespace. This is the default.
*/
static JsonWriterConfig minimal() {
return JsonWriter::new;
}
/**
* Write JSON in pretty-print, with each value on a separate line and an indentation of two
* spaces.
*/
static JsonWriterConfig prettyPrint(int n) {
return new PrettyPrint(n);
}
/**
* Enables human readable JSON output by inserting whitespace between values.after commas and
* colons. Example:
*
* <pre>
* jsonValue.writeTo(writer, WriterConfig.prettyPrint());
* </pre>
*/
class PrettyPrint implements JsonWriterConfig {
private final char[] indentChars;
PrettyPrint(char[] indentChars) {
this.indentChars = indentChars;
}
/**
* Print every value on a separate line. Use the given number of spaces for indentation.
*
* @param number the number of spaces to use
*/
PrettyPrint(int number) {
this(fillChars(number));
}
private static char[] fillChars(int number) {
if (number < 0) {
throw new IllegalArgumentException("number is negative");
}
char[] chars = new char[number];
Arrays.fill(chars, ' ');
return chars;
}
@Override
public JsonWriter createWriter(Writer writer) {
return new PrettyPrintWriter(writer, indentChars);
}
}
class PrettyPrintWriter extends JsonWriter {
private final char[] indentChars;
private int indent;
private PrettyPrintWriter(Writer writer, char[] indentChars) {
super(writer);
this.indentChars = indentChars;
}
@Override
protected void writeArrayOpen() throws IOException {
indent++;
writer.write('[');
writeNewLine();
}
@Override
protected void writeArrayClose() throws IOException {
indent--;
writeNewLine();
writer.write(']');
}
@Override
protected void writeArraySeparator() throws IOException {
writer.write(',');
if (!writeNewLine()) {
writer.write(' ');
}
}
@Override
protected void writeObjectOpen() throws IOException {
indent++;
writer.write('{');
writeNewLine();
}
@Override
protected void writeObjectClose() throws IOException {
indent--;
writeNewLine();
writer.write('}');
}
@Override
protected void writeMemberSeparator() throws IOException {
writer.write(':');
writer.write(' ');
}
@Override
protected void writeObjectSeparator() throws IOException {
writer.write(',');
if (!writeNewLine()) {
writer.write(' ');
}
}
private boolean writeNewLine() throws IOException {
if (indentChars == null) {
return false;
}
writer.write('\n');
for (int i = 0; i < indent; i++) {
writer.write(indentChars);
}
return true;
}
}
}

View file

@ -67,7 +67,7 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
private Writer writer; private Writer writer;
private JsonWriter jsonWriter; private JsonBuilder jsonBuilder;
private Marc.Builder builder; private Marc.Builder builder;
@ -90,6 +90,7 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
private String index; private String index;
private String indexType; private String indexType;
/** /**
* Flag for indicating if writer is at top of file. * Flag for indicating if writer is at top of file.
*/ */
@ -111,7 +112,7 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
public MarcJsonWriter(Writer writer, int bufferSize) { public MarcJsonWriter(Writer writer, int bufferSize) {
this.writer = new BufferedWriter(writer, bufferSize); this.writer = new BufferedWriter(writer, bufferSize);
this.jsonWriter = new JsonWriter(this.writer); this.jsonBuilder = new JsonBuilder(this.writer);
this.bufferSize = bufferSize; this.bufferSize = bufferSize;
this.lock = new ReentrantLock(); this.lock = new ReentrantLock();
this.builder = Marc.builder(); this.builder = Marc.builder();
@ -143,6 +144,10 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
return this; return this;
} }
public JsonBuilder getJsonBuilder() {
return jsonBuilder;
}
public MarcJsonWriter setIndex(String index, String indexType) { public MarcJsonWriter setIndex(String index, String indexType) {
this.index = index; this.index = index;
this.indexType = indexType; this.indexType = indexType;
@ -182,14 +187,20 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
@Override @Override
public void startDocument() { public void startDocument() {
// nothing to do here if (style.contains(Style.EMBEDDED_RECORD)) {
try {
jsonBuilder.beginMap();
} catch (IOException e) {
handleException(e);
}
}
} }
@Override @Override
public void beginCollection() { public void beginCollection() {
if (style.contains(Style.ARRAY)) { if (style.contains(Style.ARRAY)) {
try { try {
jsonWriter.writeArrayOpen(); jsonBuilder.beginCollection();
} catch (IOException e) { } catch (IOException e) {
handleException(e); handleException(e);
} }
@ -257,13 +268,13 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
public void endCollection() { public void endCollection() {
if (style.contains(Style.ARRAY)) { if (style.contains(Style.ARRAY)) {
try { try {
jsonWriter.writeArrayClose(); jsonBuilder.endCollection();
} catch (IOException e) { } catch (IOException e) {
handleException(e); handleException(e);
} }
} }
if (style.contains(Style.ELASTICSEARCH_BULK)) { if (style.contains(Style.ELASTICSEARCH_BULK)) {
// finish with line-feed "\n", not with System.lineSeparator() // finish with line-feed "\n", not with System.lineSeparator() !!!
try { try {
writer.write("\n"); writer.write("\n");
} catch (IOException e) { } catch (IOException e) {
@ -279,13 +290,25 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
@Override @Override
public void endDocument() { public void endDocument() {
try { if (style.contains(Style.EMBEDDED_RECORD)) {
flush(); try {
} catch (IOException e) { jsonBuilder.endMap();
handleException(e); } catch (IOException e) {
handleException(e);
}
} else {
try {
flush();
} catch (IOException e) {
handleException(e);
}
} }
} }
public void write(Map<String, Object> map) {
}
/** /**
* Write MARC record using fields, indicators, and subfield structures, * Write MARC record using fields, indicators, and subfield structures,
* therefore allowing duplicate keys in the output. * therefore allowing duplicate keys in the output.
@ -303,67 +326,47 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
} }
} else { } else {
if (style.contains(Style.ARRAY)) { if (style.contains(Style.ARRAY)) {
jsonWriter.writeArraySeparator(); writer.write(",");
} else if (style.contains(Style.LINES)) { } else if (style.contains(Style.LINES)) {
jsonWriter.writeLiteral("\n"); writer.append(System.lineSeparator());
} else if (style.contains(Style.ELASTICSEARCH_BULK)) { } else if (style.contains(Style.ELASTICSEARCH_BULK)) {
jsonWriter.writeLiteral("\n"); writer.append(System.lineSeparator());
writeMetaDataLine(marcRecord); writeMetaDataLine(marcRecord);
} }
} }
jsonWriter.writeObjectOpen(); if (!style.contains(Style.EMBEDDED_RECORD)) {
jsonBuilder.beginMap();
}
if (marcRecord.getFormat() != null) { if (marcRecord.getFormat() != null) {
jsonWriter.writeMemberName(FORMAT_TAG); jsonBuilder.buildKey(FORMAT_TAG).buildValue(marcRecord.getFormat());
jsonWriter.writeMemberSeparator();
jsonWriter.writeString(marcRecord.getFormat());
jsonWriter.writeObjectSeparator();
} }
if (marcRecord.getType() != null) { if (marcRecord.getType() != null) {
jsonWriter.writeMemberName(TYPE_TAG); jsonBuilder.buildKey(TYPE_TAG).buildValue(marcRecord.getType());
jsonWriter.writeMemberSeparator();
jsonWriter.writeString(marcRecord.getType());
jsonWriter.writeObjectSeparator();
} }
if (!RecordLabel.EMPTY.equals(marcRecord.getRecordLabel())) { if (!RecordLabel.EMPTY.equals(marcRecord.getRecordLabel())) {
jsonWriter.writeMemberName(LEADER_TAG); jsonBuilder.buildKey(LEADER_TAG).buildValue(marcRecord.getRecordLabel().toString());
jsonWriter.writeMemberSeparator();
jsonWriter.writeString(marcRecord.getRecordLabel().toString());
jsonWriter.writeObjectSeparator();
} }
boolean fieldseparator = false;
for (MarcField marcField : marcRecord.getFields()) { for (MarcField marcField : marcRecord.getFields()) {
if (fieldseparator) { jsonBuilder.buildKey(marcField.getTag());
jsonWriter.writeObjectSeparator();
}
jsonWriter.writeMemberName(marcField.getTag());
jsonWriter.writeMemberSeparator();
if (marcField.isControl()) { if (marcField.isControl()) {
jsonWriter.writeArrayOpen(); jsonBuilder.buildValue(marcField.recoverControlFieldValue());
jsonWriter.writeString(marcField.getValue());
jsonWriter.writeArrayClose();
} else { } else {
jsonWriter.writeObjectOpen(); jsonBuilder.beginMap();
jsonWriter.writeMemberName(marcField.getIndicator()); jsonBuilder.buildKey(marcField.getIndicator());
jsonWriter.writeMemberSeparator(); jsonBuilder.beginCollection();
jsonWriter.writeArrayOpen();
boolean subfieldseparator = false;
for (MarcField.Subfield subfield : marcField.getSubfields()) { for (MarcField.Subfield subfield : marcField.getSubfields()) {
if (subfieldseparator) { jsonBuilder.beginMap();
jsonWriter.writeObjectSeparator(); jsonBuilder.buildKey(subfield.getId());
} jsonBuilder.buildValue(subfield.getValue());
jsonWriter.writeObjectOpen(); jsonBuilder.endMap();
jsonWriter.writeMemberName(subfield.getId());
jsonWriter.writeMemberSeparator();
jsonWriter.writeString(subfield.getValue());
jsonWriter.writeObjectClose();
subfieldseparator = true;
} }
jsonWriter.writeArrayClose(); jsonBuilder.endCollection();
jsonWriter.writeObjectClose(); jsonBuilder.endMap();
} }
fieldseparator = true;
} }
jsonWriter.writeObjectClose(); if (!style.contains(Style.EMBEDDED_RECORD)) {
jsonBuilder.endMap();
}
} }
/** /**
@ -385,14 +388,16 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
if (style.contains(Style.ARRAY)) { if (style.contains(Style.ARRAY)) {
writer.write(","); writer.write(",");
} else if (style.contains(Style.LINES)) { } else if (style.contains(Style.LINES)) {
writer.write("\n"); writer.write(System.lineSeparator());
} else if (style.contains(Style.ELASTICSEARCH_BULK)) { } else if (style.contains(Style.ELASTICSEARCH_BULK)) {
writer.write("\n"); writer.write(System.lineSeparator());
writeMetaDataLine(marcRecord); writeMetaDataLine(marcRecord);
} }
} }
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("{"); if (!style.contains(Style.EMBEDDED_RECORD)) {
sb.append("{");
}
int c0 = 0; int c0 = 0;
for (Map.Entry<String, Object> tags : marcRecord.entrySet()) { for (Map.Entry<String, Object> tags : marcRecord.entrySet()) {
if (c0 > 0) { if (c0 > 0) {
@ -514,7 +519,9 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
} }
c0++; c0++;
} }
sb.append('}'); if (!style.contains(Style.EMBEDDED_RECORD)) {
sb.append('}');
}
writer.write(sb.toString()); writer.write(sb.toString());
} }
@ -561,7 +568,8 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
writer = new OutputStreamWriter(compress ? writer = new OutputStreamWriter(compress ?
new CompressedOutputStream(out, bufferSize) : new CompressedOutputStream(out, bufferSize) :
new BufferedOutputStream(out, bufferSize), StandardCharsets.UTF_8); new BufferedOutputStream(out, bufferSize), StandardCharsets.UTF_8);
jsonWriter = new JsonWriter(writer); //jsonWriter = new JsonWriter(writer);
jsonBuilder = new JsonBuilder(writer);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -582,7 +590,7 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
"\"_index\":\"" + index + "\"," + "\"_index\":\"" + index + "\"," +
"\"_type\":\"" + indexType + "\"," + "\"_type\":\"" + indexType + "\"," +
"\"_id\":\"" + id + "\"}}" + "\"_id\":\"" + id + "\"}}" +
"\n"); System.lineSeparator());
} catch (IOException e) { } catch (IOException e) {
handleException(e); handleException(e);
} }
@ -601,34 +609,20 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
for (int i = 0; i < value.length(); i++) { for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i); char c = value.charAt(i);
switch (c) { switch (c) {
case '"': case '"' -> sb.append("\\\"");
sb.append("\\\""); case '\\' -> sb.append("\\\\");
break; case '\b' -> sb.append("\\b");
case '\\': case '\f' -> sb.append("\\f");
sb.append("\\\\"); case '\n' -> sb.append("\\n");
break; case '\r' -> sb.append("\\r");
case '\b': case '\t' -> sb.append("\\t");
sb.append("\\b"); default -> {
break;
case '\f':
sb.append("\\f");
break;
case '\n':
sb.append("\\n");
break;
case '\r':
sb.append("\\r");
break;
case '\t':
sb.append("\\t");
break;
default:
if (c < 0x1f) { if (c < 0x1f) {
sb.append("\\u").append(String.format("%04x", (int) c)); sb.append("\\u").append(String.format("%04x", (int) c));
} else { } else {
sb.append(c); sb.append(c);
} }
break; }
} }
} }
return sb.toString(); return sb.toString();
@ -638,7 +632,11 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
* *
*/ */
public enum Style { public enum Style {
ARRAY, LINES, ELASTICSEARCH_BULK, ALLOW_DUPLICATES ARRAY,
LINES,
ELASTICSEARCH_BULK,
ALLOW_DUPLICATES,
EMBEDDED_RECORD
} }
/** /**
@ -651,5 +649,4 @@ public class MarcJsonWriter extends MarcContentHandler implements Flushable, Clo
def.setLevel(Deflater.BEST_COMPRESSION); def.setLevel(Deflater.BEST_COMPRESSION);
} }
} }
} }

View file

@ -350,19 +350,7 @@ public class MarcXchangeWriter extends MarcContentHandler implements Flushable,
return; return;
} }
if (field.isControl()) { if (field.isControl()) {
String value = field.getValue(); String value = field.recoverControlFieldValue();
if (value == null || value.isEmpty()) {
// the control field is disguised as a data field, try lookup value in first subfield of "_"
value = field.getFirstSubfieldValue("_");
// if no value, maybe " "?
if (value == null || value.isEmpty()) {
value = field.getFirstSubfieldValue(" ");
}
// still no value? Then it is some exotic like MAB with subfield "a"?
if (value == null || value.isEmpty()) {
value = field.getFirstSubfieldValue("a");
}
}
if (value != null && !value.isEmpty()) { if (value != null && !value.isEmpty()) {
Iterator<Attribute> attrs = Collections.singletonList(eventFactory.createAttribute(TAG_ATTRIBUTE, Iterator<Attribute> attrs = Collections.singletonList(eventFactory.createAttribute(TAG_ATTRIBUTE,
transform(field.getTag()))).iterator(); transform(field.getTag()))).iterator();

View file

@ -1,522 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import java.io.IOException;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
public class JsonArrayTest {
private JsonArray array;
private static JsonArray array(String... values) {
JsonArray array = new JsonArray();
for (String value : values) {
array.add(value);
}
return array;
}
@BeforeEach
public void setUp() {
array = new JsonArray();
}
@Test
public void copyConstructorFailsWithNull() {
Assertions.assertThrows(NullPointerException.class, () -> new JsonArray(null));
}
@Test
public void copyConstructorHasSameValues() {
array.add(23);
JsonArray copy = new JsonArray(array);
assertEquals(array.values(), copy.values());
}
@Test
public void copyConstructorworksOnSafeCopy() {
JsonArray copy = new JsonArray(array);
array.add(23);
assertTrue(copy.isEmpty());
}
@Test
public void isEmptyisTrueAfterCreation() {
assertTrue(array.isEmpty());
}
@Test
public void isEmptyisFalseAfterAdd() {
array.add(true);
assertFalse(array.isEmpty());
}
@Test
public void sizeisZeroAfterCreation() {
assertEquals(0, array.size());
}
@Test
public void sizeisOneAfterAdd() {
array.add(true);
assertEquals(1, array.size());
}
@Test
public void iteratorisEmptyAfterCreation() {
assertFalse(array.iterator().hasNext());
}
@Test
public void iteratorhasNextAfterAdd() {
array.add(true);
Iterator<JsonValue> iterator = array.iterator();
assertTrue(iterator.hasNext());
assertEquals(JsonLiteral.TRUE, iterator.next());
assertFalse(iterator.hasNext());
}
@Test
public void iteratordoesNotAllowModification() {
Assertions.assertThrows(UnsupportedOperationException.class, () -> {
array.add(23);
Iterator<JsonValue> iterator = array.iterator();
iterator.next();
iterator.remove();
});
}
@Test
public void iteratordetectsConcurrentModification() {
Assertions.assertThrows(ConcurrentModificationException.class, () -> {
Iterator<JsonValue> iterator = array.iterator();
array.add(23);
iterator.next();
});
}
@Test
public void valuesisEmptyAfterCreation() {
assertTrue(array.values().isEmpty());
}
@Test
public void valuescontainsValueAfterAdd() {
array.add(true);
assertEquals(1, array.values().size());
assertEquals(JsonLiteral.TRUE, array.values().get(0));
}
@Test
public void valuesReflectsChanges() {
List<JsonValue> values = array.values();
array.add(true);
assertEquals(array.values(), values);
}
@Test
public void valuesPreventsModification() {
List<JsonValue> values = array.values();
values.add(JsonLiteral.TRUE);
}
@Test
public void getreturnsValue() {
array.add(23);
JsonValue value = array.get(0);
assertEquals(Json.of(23), value);
}
@Test
public void getfailsWithInvalidIndex() {
Assertions.assertThrows(IndexOutOfBoundsException.class, () -> array.get(0));
}
@Test
public void addint() {
array.add(23);
assertEquals("[23]", array.toString());
}
@Test
public void addintenablesChaining() {
assertSame(array, array.add(23));
}
@Test
public void addlong() {
array.add(23L);
assertEquals("[23]", array.toString());
}
@Test
public void addlongenablesChaining() {
assertSame(array, array.add(23L));
}
@Test
public void addfloat() {
array.add(3.14f);
assertEquals("[3.14]", array.toString());
}
@Test
public void addfloatenablesChaining() {
assertSame(array, array.add(3.14f));
}
@Test
public void adddouble() {
array.add(3.14d);
assertEquals("[3.14]", array.toString());
}
@Test
public void adddoubleenablesChaining() {
assertSame(array, array.add(3.14d));
}
@Test
public void addboolean() {
array.add(true);
assertEquals("[true]", array.toString());
}
@Test
public void addbooleanenablesChaining() {
assertSame(array, array.add(true));
}
@Test
public void addstring() {
array.add("foo");
assertEquals("[\"foo\"]", array.toString());
}
@Test
public void addstringenablesChaining() {
assertSame(array, array.add("foo"));
}
@Test
public void addstringtoleratesNull() {
array.add((String) null);
assertEquals("[null]", array.toString());
}
@Test
public void addjsonNull() {
array.add(JsonLiteral.NULL);
assertEquals("[null]", array.toString());
}
@Test
public void addjsonArray() {
array.add(new JsonArray());
assertEquals("[[]]", array.toString());
}
@Test
public void addjsonObject() {
array.add(new JsonObject());
assertEquals("[{}]", array.toString());
}
@Test
public void addjsonenablesChaining() {
assertSame(array, array.add(JsonLiteral.NULL));
}
@Test
public void addjsonfailsWithNull() {
Assertions.assertThrows(NullPointerException.class, () -> array.add((JsonValue) null));
}
@Test
public void addjsonnestedArray() {
JsonArray innerArray = new JsonArray();
innerArray.add(23);
array.add(innerArray);
assertEquals("[[23]]", array.toString());
}
@Test
public void addjsonnestedArraymodifiedAfterAdd() {
JsonArray innerArray = new JsonArray();
array.add(innerArray);
innerArray.add(23);
assertEquals("[[23]]", array.toString());
}
@Test
public void addjsonnestedObject() {
JsonObject innerObject = new JsonObject();
innerObject.add("a", 23);
array.add(innerObject);
assertEquals("[{\"a\":23}]", array.toString());
}
@Test
public void addjsonnestedObjectmodifiedAfterAdd() {
JsonObject innerObject = new JsonObject();
array.add(innerObject);
innerObject.add("a", 23);
assertEquals("[{\"a\":23}]", array.toString());
}
@Test
public void setint() {
array.add(false);
array.set(0, 23);
assertEquals("[23]", array.toString());
}
@Test
public void setintenablesChaining() {
array.add(false);
assertSame(array, array.set(0, 23));
}
@Test
public void setlong() {
array.add(false);
array.set(0, 23L);
assertEquals("[23]", array.toString());
}
@Test
public void setlongenablesChaining() {
array.add(false);
assertSame(array, array.set(0, 23L));
}
@Test
public void setfloat() {
array.add(false);
array.set(0, 3.14f);
assertEquals("[3.14]", array.toString());
}
@Test
public void setfloatenablesChaining() {
array.add(false);
assertSame(array, array.set(0, 3.14f));
}
@Test
public void setdouble() {
array.add(false);
array.set(0, 3.14d);
assertEquals("[3.14]", array.toString());
}
@Test
public void setdoubleenablesChaining() {
array.add(false);
assertSame(array, array.set(0, 3.14d));
}
@Test
public void setboolean() {
array.add(false);
array.set(0, true);
assertEquals("[true]", array.toString());
}
@Test
public void setbooleanenablesChaining() {
array.add(false);
assertSame(array, array.set(0, true));
}
@Test
public void setstring() {
array.add(false);
array.set(0, "foo");
assertEquals("[\"foo\"]", array.toString());
}
@Test
public void setstringenablesChaining() {
array.add(false);
assertSame(array, array.set(0, "foo"));
}
@Test
public void setjsonNull() {
array.add(false);
array.set(0, JsonLiteral.NULL);
assertEquals("[null]", array.toString());
}
@Test
public void setjsonArray() {
array.add(false);
array.set(0, new JsonArray());
assertEquals("[[]]", array.toString());
}
@Test
public void setjsonObject() {
array.add(false);
array.set(0, new JsonObject());
assertEquals("[{}]", array.toString());
}
@Test
public void setJsonFailsWithNull() {
array.add(false);
Assertions.assertThrows(NullPointerException.class, () ->
array.set(0, (JsonValue) null));
}
@Test
public void setjsonfailsWithInvalidIndex() {
Assertions.assertThrows(IndexOutOfBoundsException.class, () ->
array.set(0, JsonLiteral.NULL));
}
@Test
public void setjsonenablesChaining() {
array.add(false);
assertSame(array, array.set(0, JsonLiteral.NULL));
}
@Test
public void setjsonreplacesDifferntArrayElements() {
array.add(3).add(6).add(9);
array.set(1, 4).set(2, 5);
assertEquals("[3,4,5]", array.toString());
}
@Test
public void removefailsWithInvalidIndex() {
Assertions.assertThrows(IndexOutOfBoundsException.class, () ->
array.remove(0)
);
}
@Test
public void removeremovesElement() {
array.add(23);
array.remove(0);
assertEquals("[]", array.toString());
}
@Test
public void removekeepsOtherElements() {
array.add("a").add("b").add("c");
array.remove(1);
assertEquals("[\"a\",\"c\"]", array.toString());
}
@Test
public void writeempty() throws IOException {
JsonWriter writer = mock(JsonWriter.class);
array.write(writer);
InOrder inOrder = inOrder(writer);
inOrder.verify(writer).writeArrayOpen();
inOrder.verify(writer).writeArrayClose();
inOrder.verifyNoMoreInteractions();
}
@Test
public void writewithSingleValue() throws IOException {
JsonWriter writer = mock(JsonWriter.class);
array.add(23);
array.write(writer);
InOrder inOrder = inOrder(writer);
inOrder.verify(writer).writeArrayOpen();
inOrder.verify(writer).writeNumber("23");
inOrder.verify(writer).writeArrayClose();
inOrder.verifyNoMoreInteractions();
}
@Test
public void writewithMultipleValues() throws IOException {
JsonWriter writer = mock(JsonWriter.class);
array.add(23).add("foo").add(false);
array.write(writer);
InOrder inOrder = inOrder(writer);
inOrder.verify(writer).writeArrayOpen();
inOrder.verify(writer).writeNumber("23");
inOrder.verify(writer).writeArraySeparator();
inOrder.verify(writer).writeString("foo");
inOrder.verify(writer).writeArraySeparator();
inOrder.verify(writer).writeLiteral("false");
inOrder.verify(writer).writeArrayClose();
inOrder.verifyNoMoreInteractions();
}
@Test
public void isArray() {
assertTrue(array.isArray());
}
@Test
public void asArray() {
assertSame(array, array.asArray());
}
@Test
public void equalstrueForEqualArrays() {
assertEquals(array(), array());
assertEquals(array("foo", "bar"), array("foo", "bar"));
}
@Test
public void equalsfalseForDifferentArrays() {
assertNotEquals(array("foo", "bar"), array("foo", "bar", "baz"));
assertNotEquals(array("foo", "bar"), array("bar", "foo"));
}
@Test
public void equalsfalseForNull() {
assertNotEquals(null, array);
}
@Test
public void equalsfalseForSubclass() {
assertNotEquals(array, new JsonArray(array) {
});
}
@Test
public void hashCodeequalsForEqualArrays() {
assertEquals(array().hashCode(), array().hashCode());
assertEquals(array("foo").hashCode(), array("foo").hashCode());
}
@Test
public void hashCodediffersForDifferentArrays() {
assertNotEquals(array().hashCode(), array("bar").hashCode());
assertNotEquals(array("foo").hashCode(), array("bar").hashCode());
}
}

View file

@ -15,17 +15,17 @@
*/ */
package org.xbib.marc.json; package org.xbib.marc.json;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class JsonWriterTest { public class JsonBuilderTest {
private StringWriter output; private StringWriter output;
private JsonWriter writer; private JsonBuilder jsonBuilder;
private static String string(char... chars) { private static String string(char... chars) {
return String.valueOf(chars); return String.valueOf(chars);
@ -34,125 +34,110 @@ public class JsonWriterTest {
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
output = new StringWriter(); output = new StringWriter();
writer = new JsonWriter(output); jsonBuilder = new JsonBuilder(output);
} }
@Test @Test
public void writeLiteral() throws IOException { public void writeLiteral() throws IOException {
writer.writeLiteral("foo"); jsonBuilder.buildValue("foo");
assertEquals("foo", output.toString()); assertEquals("\"foo\"", output.toString());
} }
@Test @Test
public void writeNumber() throws IOException { public void writeNumber() throws IOException {
writer.writeNumber("23"); jsonBuilder.buildValue(23);
assertEquals("23", output.toString()); assertEquals("23", output.toString());
} }
@Test @Test
public void writeStringempty() throws IOException { public void writeStringEmpty() throws IOException {
writer.writeString(""); jsonBuilder.buildValue("");
assertEquals("\"\"", output.toString()); assertEquals("\"\"", output.toString());
} }
@Test @Test
public void writeStingescapesBackslashes() throws IOException { public void writeStingescapesBackslashes() throws IOException {
writer.writeString("foo\\bar"); jsonBuilder.buildValue("foo\\bar");
assertEquals("\"foo\\\\bar\"", output.toString()); assertEquals("\"foo\\\\bar\"", output.toString());
} }
@Test @Test
public void writeArrayParts() throws IOException { public void writeEmptyArray() throws IOException {
writer.writeArrayOpen(); jsonBuilder.beginCollection();
writer.writeArraySeparator(); jsonBuilder.endCollection();
writer.writeArrayClose(); assertEquals("[]", output.toString());
assertEquals("[,]", output.toString());
} }
@Test @Test
public void writeObjectParts() throws IOException { public void writeEmptyObject() throws IOException {
writer.writeObjectOpen(); jsonBuilder.beginMap();
writer.writeMemberSeparator(); jsonBuilder.endMap();
writer.writeObjectSeparator(); assertEquals("{}", output.toString());
writer.writeObjectClose();
assertEquals("{:,}", output.toString());
}
@Test
public void writeMemberNameempty() throws IOException {
writer.writeMemberName("");
assertEquals("\"\"", output.toString());
}
@Test
public void writeMemberNameescapesBackslashes() throws IOException {
writer.writeMemberName("foo\\bar");
assertEquals("\"foo\\\\bar\"", output.toString());
} }
@Test @Test
public void escapesQuotes() throws IOException { public void escapesQuotes() throws IOException {
writer.writeString("a\"b"); jsonBuilder.buildValue("a\"b");
assertEquals("\"a\\\"b\"", output.toString()); assertEquals("\"a\\\"b\"", output.toString());
} }
@Test @Test
public void escapesEscapedQuotes() throws IOException { public void escapesEscapedQuotes() throws IOException {
writer.writeString("foo\\\"bar"); jsonBuilder.buildValue("foo\\\"bar");
assertEquals("\"foo\\\\\\\"bar\"", output.toString()); assertEquals("\"foo\\\\\\\"bar\"", output.toString());
} }
@Test @Test
public void escapesNewLine() throws IOException { public void escapesNewLine() throws IOException {
writer.writeString("foo\nbar"); jsonBuilder.buildValue("foo\nbar");
assertEquals("\"foo\\nbar\"", output.toString()); assertEquals("\"foo\\nbar\"", output.toString());
} }
@Test @Test
public void escapesWindowsNewLine() throws IOException { public void escapesWindowsNewLine() throws IOException {
writer.writeString("foo\r\nbar"); jsonBuilder.buildValue("foo\r\nbar");
assertEquals("\"foo\\r\\nbar\"", output.toString()); assertEquals("\"foo\\r\\nbar\"", output.toString());
} }
@Test @Test
public void escapesTabs() throws IOException { public void escapesTabs() throws IOException {
writer.writeString("foo\tbar"); jsonBuilder.buildValue("foo\tbar");
assertEquals("\"foo\\tbar\"", output.toString()); assertEquals("\"foo\\tbar\"", output.toString());
} }
@Test @Test
public void escapesSpecialCharacters() throws IOException { public void escapesSpecialCharacters() throws IOException {
writer.writeString("foo\u2028bar\u2029"); jsonBuilder.buildValue("foo\u2028bar\u2029");
assertEquals("\"foo\\u2028bar\\u2029\"", output.toString()); assertEquals("\"foo\\u2028bar\\u2029\"", output.toString());
} }
@Test @Test
public void escapesZeroCharacter() throws IOException { public void escapesZeroCharacter() throws IOException {
writer.writeString(string('f', 'o', 'o', (char) 0, 'b', 'a', 'r')); jsonBuilder.buildValue(string('f', 'o', 'o', (char) 0, 'b', 'a', 'r'));
assertEquals("\"foo\\u0000bar\"", output.toString()); assertEquals("\"foo\\u0000bar\"", output.toString());
} }
@Test @Test
public void escapesEscapeCharacter() throws IOException { public void escapesEscapeCharacter() throws IOException {
writer.writeString(string('f', 'o', 'o', (char) 27, 'b', 'a', 'r')); jsonBuilder.buildValue(string('f', 'o', 'o', (char) 27, 'b', 'a', 'r'));
assertEquals("\"foo\\u001bbar\"", output.toString()); assertEquals("\"foo\\u001bbar\"", output.toString());
} }
@Test @Test
public void escapesControlCharacters() throws IOException { public void escapesControlCharacters() throws IOException {
writer.writeString(string((char) 1, (char) 8, (char) 15, (char) 16, (char) 31)); jsonBuilder.buildValue(string((char) 1, (char) 8, (char) 15, (char) 16, (char) 31));
assertEquals("\"\\u0001\\u0008\\u000f\\u0010\\u001f\"", output.toString()); assertEquals("\"\\u0001\\u0008\\u000f\\u0010\\u001f\"", output.toString());
} }
@Test @Test
public void escapesFirstChar() throws IOException { public void escapesFirstChar() throws IOException {
writer.writeString(string('\\', 'x')); jsonBuilder.buildValue(string('\\', 'x'));
assertEquals("\"\\\\x\"", output.toString()); assertEquals("\"\\\\x\"", output.toString());
} }
@Test @Test
public void escapesLastChar() throws IOException { public void escapesLastChar() throws IOException {
writer.writeString(string('x', '\\')); jsonBuilder.buildValue(string('x', '\\'));
assertEquals("\"x\\\\\"", output.toString()); assertEquals("\"x\\\\\"", output.toString());
} }
} }

View file

@ -1,126 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.xbib.marc.json.JsonLiteral.FALSE;
import static org.xbib.marc.json.JsonLiteral.NULL;
import static org.xbib.marc.json.JsonLiteral.TRUE;
import org.junit.jupiter.api.Test;
import java.io.IOException;
public class JsonLiteralTest {
@Test
public void isNull() {
assertTrue(NULL.isNull());
assertFalse(TRUE.isNull());
assertFalse(FALSE.isNull());
}
@Test
public void isTrue() {
assertTrue(TRUE.isTrue());
assertFalse(NULL.isTrue());
assertFalse(FALSE.isTrue());
}
@Test
public void isFalse() {
assertTrue(FALSE.isFalse());
assertFalse(NULL.isFalse());
assertFalse(TRUE.isFalse());
}
@Test
public void isBoolean() {
assertTrue(TRUE.isBoolean());
assertTrue(FALSE.isBoolean());
assertFalse(NULL.isBoolean());
}
@Test
public void nullwrite() throws IOException {
JsonWriter writer = mock(JsonWriter.class);
NULL.write(writer);
verify(writer).writeLiteral("null");
verifyNoMoreInteractions(writer);
}
@Test
public void truewrite() throws IOException {
JsonWriter writer = mock(JsonWriter.class);
TRUE.write(writer);
verify(writer).writeLiteral("true");
verifyNoMoreInteractions(writer);
}
@Test
public void falsewrite() throws IOException {
JsonWriter writer = mock(JsonWriter.class);
FALSE.write(writer);
verify(writer).writeLiteral("false");
verifyNoMoreInteractions(writer);
}
@Test
public void nulltoString() {
assertEquals("null", NULL.toString());
}
@Test
public void truetoString() {
assertEquals("true", TRUE.toString());
}
@Test
public void falsetoString() {
assertEquals("false", FALSE.toString());
}
@Test
public void nullequals() {
assertEquals(NULL, NULL);
assertNotEquals(null, NULL);
assertNotEquals(NULL, TRUE);
assertNotEquals(NULL, FALSE);
assertNotEquals(NULL, Json.of("null"));
}
@Test
public void trueequals() {
assertEquals(TRUE, TRUE);
assertNotEquals(null, TRUE);
assertNotEquals(TRUE, FALSE);
assertNotEquals(TRUE, Boolean.TRUE);
assertNotEquals(NULL, Json.of("true"));
}
@Test
public void falseequals() {
assertEquals(FALSE, FALSE);
assertNotEquals(null, FALSE);
assertNotEquals(FALSE, TRUE);
assertNotEquals(FALSE, Boolean.FALSE);
assertNotEquals(NULL, Json.of("false"));
}
}

View file

@ -1,172 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.StringWriter;
public class JsonNumberTest {
private StringWriter output;
private JsonWriter writer;
@BeforeEach
public void setUp() {
output = new StringWriter();
writer = new JsonWriter(output);
}
@Test
public void constructorfailsWithNull() {
Assertions.assertThrows(NullPointerException.class, () -> new JsonNumber(null));
}
@Test
public void write() throws IOException {
new JsonNumber("23").write(writer);
assertEquals("23", output.toString());
}
@Test
public void toStringreturnsInputString() {
assertEquals("foo", new JsonNumber("foo").toString());
}
@Test
public void isInt() {
assertTrue(new JsonNumber("23").isInt());
}
@Test
public void asInt() {
assertEquals(23, new JsonNumber("23").asInt());
}
@Test
public void asIntfailsWithExceedingValues() {
Assertions.assertThrows(NumberFormatException.class, () -> {
new JsonNumber("10000000000").asInt();
});
}
@Test
public void asIntfailsWithExponent() {
Assertions.assertThrows(NumberFormatException.class, () -> {
new JsonNumber("1e5").asInt();
});
}
@Test
public void asIntfailsWithFractional() {
Assertions.assertThrows(NumberFormatException.class, () -> {
new JsonNumber("23.5").asInt();
});
}
@Test
public void asLong() {
assertEquals(23L, new JsonNumber("23").asLong());
}
@Test
public void asLongfailsWithExceedingValues() {
Assertions.assertThrows(NumberFormatException.class, () -> {
new JsonNumber("10000000000000000000").asLong();
});
}
@Test
public void asLongfailsWithExponent() {
Assertions.assertThrows(NumberFormatException.class, () -> {
new JsonNumber("1e5").asLong();
});
}
@Test
public void asLongfailsWithFractional() {
Assertions.assertThrows(NumberFormatException.class, () -> {
new JsonNumber("23.5").asLong();
});
}
@Test
public void asFloat() {
assertEquals(23.05f, new JsonNumber("23.05").asFloat(), 0);
}
@Test
public void asFloatreturnsInfinityForExceedingValues() {
assertEquals(Float.POSITIVE_INFINITY, new JsonNumber("1e50").asFloat(), 0);
assertEquals(Float.NEGATIVE_INFINITY, new JsonNumber("-1e50").asFloat(), 0);
}
@Test
public void asDouble() {
double result = new JsonNumber("23.05").asDouble();
assertEquals(23.05, result, 0);
}
@Test
public void asDoublereturnsInfinityForExceedingValues() {
assertEquals(Double.POSITIVE_INFINITY, new JsonNumber("1e500").asDouble(), 0);
assertEquals(Double.NEGATIVE_INFINITY, new JsonNumber("-1e500").asDouble(), 0);
}
@Test
public void equalstrueForSameInstance() {
JsonNumber number = new JsonNumber("23");
assertEquals(number, number);
}
@Test
public void equalstrueForEqualNumberStrings() {
assertEquals(new JsonNumber("23"), new JsonNumber("23"));
}
@Test
public void equalsfalseForDifferentNumberStrings() {
assertNotEquals(new JsonNumber("23"), new JsonNumber("42"));
assertNotEquals(new JsonNumber("1e+5"), new JsonNumber("1e5"));
}
@Test
public void equalsfalseForNull() {
assertNotEquals(null, new JsonNumber("23"));
}
@Test
public void equalsfalseForSubclass() {
assertNotEquals(new JsonNumber("23"), new JsonNumber("23") {
});
}
@Test
public void hashCodeequalsForEqualStrings() {
assertEquals(new JsonNumber("23").hashCode(), new JsonNumber("23").hashCode());
}
@Test
public void hashCodediffersForDifferentStrings() {
assertNotEquals(new JsonNumber("23").hashCode(), new JsonNumber("42").hashCode());
}
}

View file

@ -1,925 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import java.io.IOException;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
public class JsonObjectTest {
private JsonObject object;
private static JsonObject object(String... namesAndValues) {
JsonObject object = new JsonObject();
for (int i = 0; i < namesAndValues.length; i += 2) {
object.add(namesAndValues[i], namesAndValues[i + 1]);
}
return object;
}
@BeforeEach
public void setUp() {
object = new JsonObject();
}
@Test
public void copyConstructorfailsWithNull() {
Assertions.assertThrows(NullPointerException.class, () -> new JsonObject(null));
}
@Test
public void copyConstructorhasSameValues() {
object.add("foo", 23);
JsonObject copy = new JsonObject(object);
assertEquals(object.names(), copy.names());
assertSame(object.get("foo"), copy.get("foo"));
}
@Test
public void copyConstructorworksOnSafeCopy() {
JsonObject copy = new JsonObject(object);
object.add("foo", 23);
assertTrue(copy.isEmpty());
}
@Test
public void isEmptytrueAfterCreation() {
assertTrue(object.isEmpty());
}
@Test
public void isEmptyfalseAfterAdd() {
object.add("a", true);
assertFalse(object.isEmpty());
}
@Test
public void sizezeroAfterCreation() {
assertEquals(0, object.size());
}
@Test
public void sizeoneAfterAdd() {
object.add("a", true);
assertEquals(1, object.size());
}
@Test
public void keyRepetitionallowsMultipleEntries() {
object.add("a", true);
object.add("a", "value");
assertEquals(2, object.size());
}
@Test
public void keyRepetitiongetsLastEntry() {
object.add("a", true);
object.add("a", "value");
assertEquals("value", object.getString("a", "missing"));
}
@Test
public void keyRepetitionequalityConsidersRepetitions() {
object.add("a", true);
object.add("a", "value");
JsonObject onlyFirstProperty = new JsonObject();
onlyFirstProperty.add("a", true);
assertNotEquals(onlyFirstProperty, object);
JsonObject bothProperties = new JsonObject();
bothProperties.add("a", true);
bothProperties.add("a", "value");
assertEquals(bothProperties, object);
}
@Test
public void namesemptyAfterCreation() {
assertTrue(object.names().isEmpty());
}
@Test
public void namescontainsNameAfterAdd() {
object.add("foo", true);
List<String> names = object.names();
assertEquals(1, names.size());
assertEquals("foo", names.get(0));
}
@Test
public void namesreflectsChanges() {
List<String> names = object.names();
object.add("foo", true);
assertEquals(1, names.size());
assertEquals("foo", names.get(0));
}
@Test
public void namespreventsModification() {
Assertions.assertThrows(UnsupportedOperationException.class, () -> {
List<String> names = object.names();
names.add("foo");
});
}
@Test
public void iteratorisEmptyAfterCreation() {
assertFalse(object.iterator().hasNext());
}
@Test
public void iteratorhasNextAfterAdd() {
object.add("a", true);
Iterator<JsonObject.Member> iterator = object.iterator();
assertTrue(iterator.hasNext());
}
@Test
public void iteratornextReturnsActualValue() {
object.add("a", true);
Iterator<JsonObject.Member> iterator = object.iterator();
assertEquals(new JsonObject.Member("a", JsonLiteral.TRUE), iterator.next());
}
@Test
public void iteratornextProgressesToNextValue() {
object.add("a", true);
object.add("b", false);
Iterator<JsonObject.Member> iterator = object.iterator();
iterator.next();
assertTrue(iterator.hasNext());
assertEquals(new JsonObject.Member("b", JsonLiteral.FALSE), iterator.next());
}
@Test
public void iteratornextFailsAtEnd() {
Assertions.assertThrows(NoSuchElementException.class, () -> {
Iterator<JsonObject.Member> iterator = object.iterator();
iterator.next();
});
}
@Test
public void iteratordoesNotAllowModification() {
Assertions.assertThrows(UnsupportedOperationException.class, () -> {
object.add("a", 23);
Iterator<JsonObject.Member> iterator = object.iterator();
iterator.next();
iterator.remove();
});
}
@Test
public void iteratordetectsConcurrentModification() {
Assertions.assertThrows(ConcurrentModificationException.class, () -> {
Iterator<JsonObject.Member> iterator = object.iterator();
object.add("a", 23);
iterator.next();
});
}
@Test
public void getfailsWithNullName() {
Assertions.assertThrows(NullPointerException.class, () -> object.get(null));
}
@Test
public void getreturnsNullForNonExistingMember() {
assertNull(object.get("foo"));
}
@Test
public void getreturnsValueForName() {
object.add("foo", true);
assertEquals(JsonLiteral.TRUE, object.get("foo"));
}
@Test
public void getreturnsLastValueForName() {
object.add("foo", false).add("foo", true);
assertEquals(JsonLiteral.TRUE, object.get("foo"));
}
@Test
public void getintreturnsValueFromMember() {
object.add("foo", 23);
assertEquals(23, object.getInt("foo", 42));
}
@Test
public void getintreturnsDefaultForMissingMember() {
assertEquals(23, object.getInt("foo", 23));
}
@Test
public void getlongreturnsValueFromMember() {
object.add("foo", 23L);
assertEquals(23L, object.getLong("foo", 42L));
}
@Test
public void getlongreturnsDefaultForMissingMember() {
assertEquals(23L, object.getLong("foo", 23L));
}
@Test
public void getfloatreturnsValueFromMember() {
object.add("foo", 3.14f);
assertEquals(3.14f, object.getFloat("foo", 1.41f), 0);
}
@Test
public void getfloatreturnsDefaultForMissingMember() {
assertEquals(3.14f, object.getFloat("foo", 3.14f), 0);
}
@Test
public void getdoublereturnsValueFromMember() {
object.add("foo", 3.14);
assertEquals(3.14, object.getDouble("foo", 1.41), 0);
}
@Test
public void getdoublereturnsDefaultForMissingMember() {
assertEquals(3.14, object.getDouble("foo", 3.14), 0);
}
@Test
public void getbooleanreturnsValueFromMember() {
object.add("foo", true);
assertTrue(object.getBoolean("foo", false));
}
@Test
public void getbooleanreturnsDefaultForMissingMember() {
assertFalse(object.getBoolean("foo", false));
}
@Test
public void getstringreturnsValueFromMember() {
object.add("foo", "bar");
assertEquals("bar", object.getString("foo", "default"));
}
@Test
public void getstringreturnsDefaultForMissingMember() {
assertEquals("default", object.getString("foo", "default"));
}
@Test
public void addfailsWithNullName() {
Assertions.assertThrows(NullPointerException.class, () -> object.add(null, 23));
}
@Test
public void addint() {
object.add("a", 23);
assertEquals("{\"a\":23}", object.toString());
}
@Test
public void addintenablesChaining() {
assertSame(object, object.add("a", 23));
}
@Test
public void addlong() {
object.add("a", 23L);
assertEquals("{\"a\":23}", object.toString());
}
@Test
public void addlongenablesChaining() {
assertSame(object, object.add("a", 23L));
}
@Test
public void addfloat() {
object.add("a", 3.14f);
assertEquals("{\"a\":3.14}", object.toString());
}
@Test
public void addfloatenablesChaining() {
assertSame(object, object.add("a", 3.14f));
}
@Test
public void adddouble() {
object.add("a", 3.14d);
assertEquals("{\"a\":3.14}", object.toString());
}
@Test
public void adddoubleenablesChaining() {
assertSame(object, object.add("a", 3.14d));
}
@Test
public void addboolean() {
object.add("a", true);
assertEquals("{\"a\":true}", object.toString());
}
@Test
public void addbooleanenablesChaining() {
assertSame(object, object.add("a", true));
}
@Test
public void addstring() {
object.add("a", "foo");
assertEquals("{\"a\":\"foo\"}", object.toString());
}
@Test
public void addstringtoleratesNull() {
object.add("a", (String) null);
assertEquals("{\"a\":null}", object.toString());
}
@Test
public void addstringenablesChaining() {
assertSame(object, object.add("a", "foo"));
}
@Test
public void addjsonNull() {
object.add("a", JsonLiteral.NULL);
assertEquals("{\"a\":null}", object.toString());
}
@Test
public void addjsonArray() {
object.add("a", new JsonArray());
assertEquals("{\"a\":[]}", object.toString());
}
@Test
public void addjsonObject() {
object.add("a", new JsonObject());
assertEquals("{\"a\":{}}", object.toString());
}
@Test
public void addjsonenablesChaining() {
assertSame(object, object.add("a", JsonLiteral.NULL));
}
@Test
public void addjsonfailsWithNull() {
Assertions.assertThrows(NullPointerException.class, () ->
object.add("a", (JsonValue) null));
}
@Test
public void addjsonnestedArray() {
JsonArray innerArray = new JsonArray();
innerArray.add(23);
object.add("a", innerArray);
assertEquals("{\"a\":[23]}", object.toString());
}
@Test
public void addjsonnestedArraymodifiedAfterAdd() {
JsonArray innerArray = new JsonArray();
object.add("a", innerArray);
innerArray.add(23);
assertEquals("{\"a\":[23]}", object.toString());
}
@Test
public void addjsonnestedObject() {
JsonObject innerObject = new JsonObject();
innerObject.add("a", 23);
object.add("a", innerObject);
assertEquals("{\"a\":{\"a\":23}}", object.toString());
}
@Test
public void addjsonnestedObjectmodifiedAfterAdd() {
JsonObject innerObject = new JsonObject();
object.add("a", innerObject);
innerObject.add("a", 23);
assertEquals("{\"a\":{\"a\":23}}", object.toString());
}
@Test
public void setint() {
object.set("a", 23);
assertEquals("{\"a\":23}", object.toString());
}
@Test
public void setintenablesChaining() {
assertSame(object, object.set("a", 23));
}
@Test
public void setlong() {
object.set("a", 23L);
assertEquals("{\"a\":23}", object.toString());
}
@Test
public void setlongenablesChaining() {
assertSame(object, object.set("a", 23L));
}
@Test
public void setfloat() {
object.set("a", 3.14f);
assertEquals("{\"a\":3.14}", object.toString());
}
@Test
public void setfloatenablesChaining() {
assertSame(object, object.set("a", 3.14f));
}
@Test
public void setdouble() {
object.set("a", 3.14d);
assertEquals("{\"a\":3.14}", object.toString());
}
@Test
public void setdoubleenablesChaining() {
assertSame(object, object.set("a", 3.14d));
}
@Test
public void setboolean() {
object.set("a", true);
assertEquals("{\"a\":true}", object.toString());
}
@Test
public void setbooleanenablesChaining() {
assertSame(object, object.set("a", true));
}
@Test
public void setstring() {
object.set("a", "foo");
assertEquals("{\"a\":\"foo\"}", object.toString());
}
@Test
public void setstringenablesChaining() {
assertSame(object, object.set("a", "foo"));
}
@Test
public void setjsonNull() {
object.set("a", JsonLiteral.NULL);
assertEquals("{\"a\":null}", object.toString());
}
@Test
public void setjsonArray() {
object.set("a", new JsonArray());
assertEquals("{\"a\":[]}", object.toString());
}
@Test
public void setjsonObject() {
object.set("a", new JsonObject());
assertEquals("{\"a\":{}}", object.toString());
}
@Test
public void setjsonenablesChaining() {
assertSame(object, object.set("a", JsonLiteral.NULL));
}
@Test
public void setaddsElementIfMissing() {
object.set("a", JsonLiteral.TRUE);
assertEquals("{\"a\":true}", object.toString());
}
@Test
public void setmodifiesElementIfExisting() {
object.add("a", JsonLiteral.TRUE);
object.set("a", JsonLiteral.FALSE);
assertEquals("{\"a\":false}", object.toString());
}
@Test
public void setmodifiesLastElementIfMultipleExisting() {
object.add("a", 1);
object.add("a", 2);
object.set("a", JsonLiteral.TRUE);
assertEquals("{\"a\":1,\"a\":true}", object.toString());
}
@Test
public void removefailsWithNullName() {
Assertions.assertThrows(NullPointerException.class, () -> object.remove(null));
}
@Test
public void removeremovesMatchingMember() {
object.add("a", 23);
object.remove("a");
assertEquals("{}", object.toString());
}
@Test
public void removeremovesOnlyMatchingMember() {
object.add("a", 23);
object.add("b", 42);
object.add("c", true);
object.remove("b");
assertEquals("{\"a\":23,\"c\":true}", object.toString());
}
@Test
public void removeremovesOnlyLastMatchingMember() {
object.add("a", 23);
object.add("a", 42);
object.remove("a");
assertEquals("{\"a\":23}", object.toString());
}
@Test
public void removeremovesOnlyLastMatchingMemberafterRemove() {
object.add("a", 23);
object.remove("a");
object.add("a", 42);
object.add("a", 47);
object.remove("a");
assertEquals("{\"a\":42}", object.toString());
}
@Test
public void removedoesNotModifyObjectWithoutMatchingMember() {
object.add("a", 23);
object.remove("b");
assertEquals("{\"a\":23}", object.toString());
}
@Test
public void mergefailsWithNull() {
Assertions.assertThrows(NullPointerException.class, () -> object.merge(null));
}
@Test
public void mergeappendsMembers() {
object.add("a", 1).add("b", 1);
object.merge(Json.object().add("c", 2).add("d", 2));
assertEquals(Json.object().add("a", 1).add("b", 1).add("c", 2).add("d", 2), object);
}
@Test
public void mergereplacesMembers() {
object.add("a", 1).add("b", 1).add("c", 1);
object.merge(Json.object().add("b", 2).add("d", 2));
assertEquals(Json.object().add("a", 1).add("b", 2).add("c", 1).add("d", 2), object);
}
@Test
public void writeempty() throws IOException {
JsonWriter writer = mock(JsonWriter.class);
object.write(writer);
InOrder inOrder = inOrder(writer);
inOrder.verify(writer).writeObjectOpen();
inOrder.verify(writer).writeObjectClose();
inOrder.verifyNoMoreInteractions();
}
@Test
public void writewithSingleValue() throws IOException {
JsonWriter writer = mock(JsonWriter.class);
object.add("a", 23);
object.write(writer);
InOrder inOrder = inOrder(writer);
inOrder.verify(writer).writeObjectOpen();
inOrder.verify(writer).writeMemberName("a");
inOrder.verify(writer).writeMemberSeparator();
inOrder.verify(writer).writeNumber("23");
inOrder.verify(writer).writeObjectClose();
inOrder.verifyNoMoreInteractions();
}
@Test
public void writewithMultipleValues() throws IOException {
JsonWriter writer = mock(JsonWriter.class);
object.add("a", 23);
object.add("b", 3.14f);
object.add("c", "foo");
object.add("d", true);
object.add("e", (String) null);
object.write(writer);
InOrder inOrder = inOrder(writer);
inOrder.verify(writer).writeObjectOpen();
inOrder.verify(writer).writeMemberName("a");
inOrder.verify(writer).writeMemberSeparator();
inOrder.verify(writer).writeNumber("23");
inOrder.verify(writer).writeObjectSeparator();
inOrder.verify(writer).writeMemberName("b");
inOrder.verify(writer).writeMemberSeparator();
inOrder.verify(writer).writeNumber("3.14");
inOrder.verify(writer).writeObjectSeparator();
inOrder.verify(writer).writeMemberName("c");
inOrder.verify(writer).writeMemberSeparator();
inOrder.verify(writer).writeString("foo");
inOrder.verify(writer).writeObjectSeparator();
inOrder.verify(writer).writeMemberName("d");
inOrder.verify(writer).writeMemberSeparator();
inOrder.verify(writer).writeLiteral("true");
inOrder.verify(writer).writeObjectSeparator();
inOrder.verify(writer).writeMemberName("e");
inOrder.verify(writer).writeMemberSeparator();
inOrder.verify(writer).writeLiteral("null");
inOrder.verify(writer).writeObjectClose();
inOrder.verifyNoMoreInteractions();
}
@Test
public void isObject() {
assertTrue(object.isObject());
}
@Test
public void asObject() {
assertSame(object, object.asObject());
}
@Test
public void equalstrueForSameInstance() {
assertEquals(object, object);
}
@Test
public void equalstrueForEqualObjects() {
assertEquals(object(), object());
assertEquals(object("a", "1", "b", "2"), object("a", "1", "b", "2"));
}
@Test
public void equalsfalseForDifferentObjects() {
assertNotEquals(object("a", "1"), object("a", "2"));
assertNotEquals(object("a", "1"), object("b", "1"));
assertNotEquals(object("a", "1", "b", "2"), object("b", "2", "a", "1"));
}
@Test
public void equalsfalseForNull() {
assertNotEquals(null, new JsonObject());
}
@Test
public void equalsfalseForSubclass() {
JsonObject jsonObject = new JsonObject();
assertNotEquals(jsonObject, new JsonObject(jsonObject) {
});
}
@Test
public void hashCodeequalsForEqualObjects() {
assertEquals(object().hashCode(), object().hashCode());
assertEquals(object("a", "1").hashCode(), object("a", "1").hashCode());
}
@Test
public void hashCodediffersForDifferentObjects() {
assertNotEquals(object().hashCode(), object("a", "1").hashCode());
assertNotEquals(object("a", "1").hashCode(), object("a", "2").hashCode());
assertNotEquals(object("a", "1").hashCode(), object("b", "1").hashCode());
}
@Test
public void indexOfreturnsNoIndexIfEmpty() {
assertEquals(-1, object.indexOf("a"));
}
@Test
public void indexOfreturnsIndexOfMember() {
object.add("a", true);
assertEquals(0, object.indexOf("a"));
}
@Test
public void indexOfreturnsIndexOfLastMember() {
object.add("a", true);
object.add("a", true);
assertEquals(1, object.indexOf("a"));
}
@Test
public void indexOfreturnsIndexOfLastMemberafterRemove() {
object.add("a", true);
object.add("a", true);
object.remove("a");
assertEquals(0, object.indexOf("a"));
}
@Test
public void indexOfreturnsUpdatedIndexAfterRemove() {
// See issue #16
object.add("a", true);
object.add("b", true);
object.remove("a");
assertEquals(0, object.indexOf("b"));
}
@Test
public void indexOfreturnsIndexOfLastMemberforBigObject() {
object.add("a", true);
// for indexes above 255, the hash index table does not return a value
for (int i = 0; i < 256; i++) {
object.add("x-" + i, 0);
}
object.add("a", true);
assertEquals(257, object.indexOf("a"));
}
@Test
public void hashIndexTablecopyConstructor() {
JsonObject.HashIndexTable original = new JsonObject.HashIndexTable();
original.add("name", 23);
JsonObject.HashIndexTable copy = new JsonObject.HashIndexTable(original);
assertEquals(23, copy.get("name"));
}
@Test
public void hashIndexTableadd() {
JsonObject.HashIndexTable indexTable = new JsonObject.HashIndexTable();
indexTable.add("name-0", 0);
indexTable.add("name-1", 1);
indexTable.add("name-fe", 0xfe);
indexTable.add("name-ff", 0xff);
assertEquals(0, indexTable.get("name-0"));
assertEquals(1, indexTable.get("name-1"));
assertEquals(0xfe, indexTable.get("name-fe"));
assertEquals(-1, indexTable.get("name-ff"));
}
@Test
public void hashIndexTableaddoverwritesPreviousValue() {
JsonObject.HashIndexTable indexTable = new JsonObject.HashIndexTable();
indexTable.add("name", 23);
indexTable.add("name", 42);
assertEquals(42, indexTable.get("name"));
}
@Test
public void hashIndexTableaddclearsPreviousValueIfIndexExceeds0xff() {
JsonObject.HashIndexTable indexTable = new JsonObject.HashIndexTable();
indexTable.add("name", 23);
indexTable.add("name", 300);
assertEquals(-1, indexTable.get("name"));
}
@Test
public void hashIndexTableremove() {
JsonObject.HashIndexTable indexTable = new JsonObject.HashIndexTable();
indexTable.add("name", 23);
indexTable.remove(23);
assertEquals(-1, indexTable.get("name"));
}
@Test
public void hashIndexTableremoveupdatesSubsequentElements() {
JsonObject.HashIndexTable indexTable = new JsonObject.HashIndexTable();
indexTable.add("foo", 23);
indexTable.add("bar", 42);
indexTable.remove(23);
assertEquals(41, indexTable.get("bar"));
}
@Test
public void hashIndexTableremovedoesNotChangePrecedingElements() {
JsonObject.HashIndexTable indexTable = new JsonObject.HashIndexTable();
indexTable.add("foo", 23);
indexTable.add("bar", 42);
indexTable.remove(42);
assertEquals(23, indexTable.get("foo"));
}
@Test
public void memberreturnsNameAndValue() {
JsonObject.Member member = new JsonObject.Member("a", JsonLiteral.TRUE);
assertEquals("a", member.getName());
assertEquals(JsonLiteral.TRUE, member.getValue());
}
@Test
public void memberequalstrueForSameInstance() {
JsonObject.Member member = new JsonObject.Member("a", JsonLiteral.TRUE);
assertEquals(member, member);
}
@Test
public void memberequalstrueForEqualObjects() {
JsonObject.Member member = new JsonObject.Member("a", JsonLiteral.TRUE);
assertEquals(member, new JsonObject.Member("a", JsonLiteral.TRUE));
}
@Test
public void memberequalsfalseForDifferingObjects() {
JsonObject.Member member = new JsonObject.Member("a", JsonLiteral.TRUE);
assertNotEquals(member, new JsonObject.Member("b", JsonLiteral.TRUE));
assertNotEquals(member, new JsonObject.Member("a", JsonLiteral.FALSE));
}
@Test
public void memberequalsfalseForNull() {
JsonObject.Member member = new JsonObject.Member("a", JsonLiteral.TRUE);
assertNotNull(member);
}
@Test
public void memberequalsfalseForSubclass() {
JsonObject.Member member = new JsonObject.Member("a", JsonLiteral.TRUE);
assertNotEquals(member, new JsonObject.Member("a", JsonLiteral.TRUE) {
});
}
@Test
public void memberhashCodeequalsForEqualObjects() {
JsonObject.Member member = new JsonObject.Member("a", JsonLiteral.TRUE);
assertEquals(member.hashCode(), new JsonObject.Member("a", JsonLiteral.TRUE).hashCode());
}
@Test
public void memberhashCodediffersForDifferingobjects() {
JsonObject.Member member = new JsonObject.Member("a", JsonLiteral.TRUE);
assertNotEquals(member.hashCode(), new JsonObject.Member("b", JsonLiteral.TRUE).hashCode());
assertNotEquals(member.hashCode(), new JsonObject.Member("a", JsonLiteral.FALSE).hashCode());
}
}

View file

@ -1,812 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.StringStartsWith.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.xbib.marc.json.Json.parse;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.StringReader;
public class JsonReaderTest {
private static String join(String... strings) {
StringBuilder builder = new StringBuilder();
for (String string : strings) {
builder.append(string).append('\n');
}
return builder.toString();
}
@Test
public void constructorRejectsNullHandler() {
Assertions.assertThrows(NullPointerException.class, () -> {
new JsonReader<>(null, null);
});
}
@Test
public void parseStringrRejectsNull() {
Assertions.assertThrows(NullPointerException.class, () -> {
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader(null), new TestHandler());
reader.parse();
});
}
@Test
public void parseReaderRejectsNull() {
Assertions.assertThrows(NullPointerException.class, () -> {
JsonReader<Object, Object> reader = new JsonReader<>(null, new TestHandler());
reader.parse();
});
}
@Test
public void parseReaderRejectsNegativeBufferSize() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader("[]"), new TestHandler());
reader.parse(-1);
});
}
@Test
public void parseStringRejectsEmpty() {
assertParseException(0, "Unexpected end of input", "");
}
@Test
public void parseReaderRejectsEmpty() {
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader(""), new TestHandler());
JsonException exception = Assertions.assertThrows(JsonException.class, reader::parse);
assertThat(exception.getMessage(), startsWith("Unexpected end of input"));
}
@Test
public void parseNull() throws IOException {
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader("null"), handler);
reader.parse();
assertEquals(join("startNull ",
"endNull "),
handler.getLog());
}
@Test
public void parseTrue() throws IOException {
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader("true"), handler);
reader.parse();
assertEquals(join("startBoolean ",
"endBoolean true "),
handler.getLog());
}
@Test
public void parseFalse() throws IOException {
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader("false"), handler);
reader.parse();
assertEquals(join("startBoolean ",
"endBoolean false "),
handler.getLog());
}
@Test
public void parseString() throws IOException {
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader("\"foo\""), handler);
reader.parse();
assertEquals(join("startString ",
"endString foo "),
handler.getLog());
}
@Test
public void parseStringEmpty() throws IOException {
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader("\"\""), handler);
reader.parse();
assertEquals(join("startString ",
"endString "),
handler.getLog());
}
@Test
public void parseNumber() throws IOException {
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader("23"), handler);
reader.parse();
assertEquals(join("startNumber ",
"endNumber 23 "),
handler.getLog());
}
@Test
public void parseNumberNegative() throws IOException {
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader("-23"), handler);
reader.parse();
assertEquals(join("startNumber ",
"endNumber -23 "),
handler.getLog());
}
@Test
public void parsenumbernegativeexponent() throws IOException {
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader("-2.3e-12"), handler);
reader.parse();
assertEquals(join("startNumber ",
"endNumber -2.3e-12 "),
handler.getLog());
}
@Test
public void parsearray() throws IOException {
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader("[23]"), handler);
reader.parse();
assertEquals(join("startArray ",
"startArrayValue a1 ",
"startNumber ",
"endNumber 23 ",
"endArrayValue a1 ",
"endArray a1 "),
handler.getLog());
}
@Test
public void parsearrayempty() throws IOException {
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader("[]"), handler);
reader.parse();
assertEquals(join("startArray ",
"endArray a1 "),
handler.getLog());
}
@Test
public void parseobject() throws IOException {
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader("{\"foo\": 23}"), handler);
reader.parse();
assertEquals(join("startObject ",
"startObjectName o1 ",
"endObjectName o1 foo ",
"startObjectValue o1 foo ",
"startNumber ",
"endNumber 23 ",
"endObjectValue o1 foo ",
"endObject o1 "),
handler.getLog());
}
@Test
public void parseobjectempty() throws IOException {
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader("{}"), handler);
reader.parse();
assertEquals(join("startObject ",
"endObject o1 "),
handler.getLog());
}
@Test
public void parsestripsPadding() throws IOException {
assertEquals(new JsonArray(), parse(" [ ] "));
}
@Test
public void parseignoresAllWhiteSpace() throws IOException {
assertEquals(new JsonArray(), parse("\t\r\n [\t\r\n ]\t\r\n "));
}
@Test
public void parsefailsWithUnterminatedString() {
assertParseException(5, "Unexpected end of input", "[\"foo");
}
@Test
public void parsehandlesInputsThatExceedBufferSize() throws IOException {
String input = "[ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47 ]";
JsonDefaultHandler defHandler = new JsonDefaultHandler();
JsonReader<JsonArray, JsonObject> reader = new JsonReader<>(new StringReader(input), defHandler);
reader.parse(3);
assertEquals("[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47]", defHandler.getValue().toString());
}
@Test
public void parsehandlesStringsThatExceedBufferSize() throws IOException {
String input = "[ \"lorem ipsum dolor sit amet\" ]";
JsonDefaultHandler defHandler = new JsonDefaultHandler();
JsonReader<JsonArray, JsonObject> reader = new JsonReader<>(new StringReader(input), defHandler);
reader.parse(3);
assertEquals("[\"lorem ipsum dolor sit amet\"]", defHandler.getValue().toString());
}
@Test
public void parsehandlesNumbersThatExceedBufferSize() throws IOException {
String input = "[ 3.141592653589 ]";
JsonDefaultHandler defHandler = new JsonDefaultHandler();
JsonReader<JsonArray, JsonObject> reader = new JsonReader<>(new StringReader(input), defHandler);
reader.parse(3);
assertEquals("[3.141592653589]", defHandler.getValue().toString());
}
@Test
public void parsehandlesPositionsCorrectlyWhenInputExceedsBufferSize() {
final String input = "{\n \"a\": 23,\n \"b\": 42,\n}";
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader(input), handler);
Assertions.assertThrows(JsonException.class, () -> reader.parse(3));
}
@Test
public void parsefailsOnTooDeeplyNestedArray() {
JsonArray array = new JsonArray();
for (int i = 0; i < 1001; i++) {
array = new JsonArray().add(array);
}
final String input = array.toString();
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader(input), handler);
JsonException exception = Assertions.assertThrows(JsonException.class, reader::parse);
assertEquals("Nesting too deep", exception.getMessage());
}
@Test
public void parsefailsOnTooDeeplyNestedObject() {
JsonObject object = new JsonObject();
for (int i = 0; i < 1001; i++) {
object = new JsonObject().add("foo", object);
}
final String input = object.toString();
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader(input), handler);
JsonException exception = Assertions.assertThrows(JsonException.class, reader::parse);
assertEquals("Nesting too deep", exception.getMessage());
}
@Test
public void parsefailsOnTooDeeplyNestedMixedObject() {
JsonValue value = new JsonObject();
for (int i = 0; i < 1001; i++) {
value = i % 2 == 0 ? new JsonArray().add(value) : new JsonObject().add("foo", value);
}
final String input = value.toString();
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader(input), handler);
JsonException exception = Assertions.assertThrows(JsonException.class, reader::parse);
assertEquals("Nesting too deep", exception.getMessage());
}
@Test
public void parsedoesNotFailWithManyArrays() throws IOException {
JsonArray array = new JsonArray();
for (int i = 0; i < 1001; i++) {
array.add(new JsonArray().add(7));
}
final String input = array.toString();
JsonValue result = parse(input);
assertTrue(result.isArray());
}
@Test
public void parsedoesNotFailWithManyEmptyArrays() throws IOException {
JsonArray array = new JsonArray();
for (int i = 0; i < 1001; i++) {
array.add(new JsonArray());
}
final String input = array.toString();
JsonValue result = parse(input);
assertTrue(result.isArray());
}
@Test
public void parsedoesNotFailWithManyObjects() throws IOException {
JsonArray array = new JsonArray();
for (int i = 0; i < 1001; i++) {
array.add(new JsonObject().add("a", 7));
}
final String input = array.toString();
JsonValue result = parse(input);
assertTrue(result.isArray());
}
@Test
public void parsedoesNotFailWithManyEmptyObjects() throws IOException {
JsonArray array = new JsonArray();
for (int i = 0; i < 1001; i++) {
array.add(new JsonObject());
}
final String input = array.toString();
JsonValue result = parse(input);
assertTrue(result.isArray());
}
@Test
public void parseCanBeCalledOnce() throws IOException {
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader("[23]"), handler);
reader.parse();
assertEquals(join(
"startArray ",
"startArrayValue a1 ",
"startNumber ",
"endNumber 23 ",
"endArrayValue a1 ",
"endArray a1 "),
handler.getLog());
handler = new TestHandler();
reader = new JsonReader<>(new StringReader("[42]"), handler);
reader.parse();
assertEquals(join(
"startArray ",
"startArrayValue a1 ",
"startNumber ",
"endNumber 42 ",
"endArrayValue a1 ",
"endArray a1 "),
handler.getLog());
}
@Test
public void arraysempty() throws IOException {
assertEquals("[]", parse("[]").toString());
}
@Test
public void arrayssingleValue() throws IOException {
assertEquals("[23]", parse("[23]").toString());
}
@Test
public void arraysmultipleValues() throws IOException {
assertEquals("[23,42]", parse("[23,42]").toString());
}
@Test
public void arrayswithWhitespaces() throws IOException {
assertEquals("[23,42]", parse("[ 23 , 42 ]").toString());
}
@Test
public void arraysnested() throws IOException {
assertEquals("[[23]]", parse("[[23]]").toString());
assertEquals("[[[]]]", parse("[[[]]]").toString());
assertEquals("[[23],42]", parse("[[23],42]").toString());
assertEquals("[[23],[42]]", parse("[[23],[42]]").toString());
assertEquals("[[23],[42]]", parse("[[23],[42]]").toString());
assertEquals("[{\"foo\":[23]},{\"bar\":[42]}]",
parse("[{\"foo\":[23]},{\"bar\":[42]}]").toString());
}
@Test
public void arraysillegalSyntax() {
assertParseException(1, "Expected value", "[,]");
assertParseException(4, "Expected ',' or ']'", "[23 42]");
assertParseException(4, "Expected value", "[23,]");
}
@Test
public void arraysincomplete() {
assertParseException(1, "Unexpected end of input", "[");
assertParseException(2, "Unexpected end of input", "[ ");
assertParseException(3, "Unexpected end of input", "[23");
assertParseException(4, "Unexpected end of input", "[23 ");
assertParseException(4, "Unexpected end of input", "[23,");
assertParseException(5, "Unexpected end of input", "[23, ");
}
@Test
public void objectsempty() throws IOException {
assertEquals("{}", parse("{}").toString());
}
@Test
public void objectssingleValue() throws IOException {
assertEquals("{\"foo\":23}", parse("{\"foo\":23}").toString());
}
@Test
public void objectsmultipleValues() throws IOException {
assertEquals("{\"foo\":23,\"bar\":42}", parse("{\"foo\":23,\"bar\":42}").toString());
}
@Test
public void objectswhitespace() throws IOException {
assertEquals("{\"foo\":23,\"bar\":42}", parse("{ \"foo\" : 23, \"bar\" : 42 }").toString());
}
@Test
public void objectsnested() throws IOException {
assertEquals("{\"foo\":{}}", parse("{\"foo\":{}}").toString());
assertEquals("{\"foo\":{\"bar\":42}}", parse("{\"foo\":{\"bar\": 42}}").toString());
assertEquals("{\"foo\":{\"bar\":{\"baz\":42}}}",
parse("{\"foo\":{\"bar\": {\"baz\": 42}}}").toString());
assertEquals("{\"foo\":[{\"bar\":{\"baz\":[[42]]}}]}",
parse("{\"foo\":[{\"bar\": {\"baz\": [[42]]}}]}").toString());
}
@Test
public void objectsillegalSyntax() {
assertParseException(1, "Expected name", "{,}");
assertParseException(1, "Expected name", "{:}");
assertParseException(1, "Expected name", "{23}");
assertParseException(4, "Expected ':'", "{\"a\"}");
assertParseException(5, "Expected ':'", "{\"a\" \"b\"}");
assertParseException(5, "Expected value", "{\"a\":}");
assertParseException(8, "Expected name", "{\"a\":23,}");
assertParseException(8, "Expected name", "{\"a\":23,42");
}
@Test
public void objectsincomplete() {
assertParseException(1, "Unexpected end of input", "{");
assertParseException(2, "Unexpected end of input", "{ ");
assertParseException(2, "Unexpected end of input", "{\"");
assertParseException(4, "Unexpected end of input", "{\"a\"");
assertParseException(5, "Unexpected end of input", "{\"a\" ");
assertParseException(5, "Unexpected end of input", "{\"a\":");
assertParseException(6, "Unexpected end of input", "{\"a\": ");
assertParseException(7, "Unexpected end of input", "{\"a\":23");
assertParseException(8, "Unexpected end of input", "{\"a\":23 ");
assertParseException(8, "Unexpected end of input", "{\"a\":23,");
assertParseException(9, "Unexpected end of input", "{\"a\":23, ");
}
@Test
public void stringsemptyStringisAccepted() throws IOException {
assertEquals("", parse("\"\"").asString());
}
@Test
public void stringsasciiCharactersareAccepted() throws IOException {
assertEquals(" ", parse("\" \"").asString());
assertEquals("a", parse("\"a\"").asString());
assertEquals("foo", parse("\"foo\"").asString());
assertEquals("A2-D2", parse("\"A2-D2\"").asString());
assertEquals("\u007f", parse("\"\u007f\"").asString());
}
@Test
public void stringsnonAsciiCharactersareAccepted() throws IOException {
assertEquals("Русский", parse("\"Русский\"").asString());
assertEquals("العربية", parse("\"العربية\"").asString());
assertEquals("日本語", parse("\"日本語\"").asString());
}
@Test
public void stringscontrolCharactersareRejected() {
// JSON string must not contain characters < 0x20
assertParseException(3, "Expected valid string character", "\"--\n--\"");
assertParseException(3, "Expected valid string character", "\"--\r\n--\"");
assertParseException(3, "Expected valid string character", "\"--\t--\"");
assertParseException(3, "Expected valid string character", "\"--\u0000--\"");
assertParseException(3, "Expected valid string character", "\"--\u001f--\"");
}
@Test
public void stringsvalidEscapesareAccepted() throws IOException {
// valid escapes are \" \\ \/ \b \f \n \r \t and unicode escapes
assertEquals(" \" ", parse("\" \\\" \"").asString());
assertEquals(" \\ ", parse("\" \\\\ \"").asString());
assertEquals(" / ", parse("\" \\/ \"").asString());
assertEquals(" \u0008 ", parse("\" \\b \"").asString());
assertEquals(" \u000c ", parse("\" \\f \"").asString());
assertEquals(" \r ", parse("\" \\r \"").asString());
assertEquals(" \n ", parse("\" \\n \"").asString());
assertEquals(" \t ", parse("\" \\t \"").asString());
}
@Test
public void stringsescapeatStart() throws IOException {
assertEquals("\\x", parse("\"\\\\x\"").asString());
}
@Test
public void stringsescapeatEnd() throws IOException {
assertEquals("x\\", parse("\"x\\\\\"").asString());
}
@Test
public void stringsillegalEscapesareRejected() {
assertParseException(2, "Expected valid escape sequence", "\"\\a\"");
assertParseException(2, "Expected valid escape sequence", "\"\\x\"");
assertParseException(2, "Expected valid escape sequence", "\"\\000\"");
}
@Test
public void stringsvalidUnicodeEscapesareAccepted() throws IOException {
assertEquals("\u0021", parse("\"\\u0021\"").asString());
assertEquals("\u4711", parse("\"\\u4711\"").asString());
assertEquals("\uffff", parse("\"\\uffff\"").asString());
assertEquals("\uabcdx", parse("\"\\uabcdx\"").asString());
}
@Test
public void stringsillegalUnicodeEscapesareRejected() {
assertParseException(3, "Expected hexadecimal digit", "\"\\u \"");
assertParseException(3, "Expected hexadecimal digit", "\"\\ux\"");
assertParseException(5, "Expected hexadecimal digit", "\"\\u20 \"");
assertParseException(6, "Expected hexadecimal digit", "\"\\u000x\"");
}
@Test
public void stringsincompleteStringsareRejected() {
assertParseException(1, "Unexpected end of input", "\"");
assertParseException(4, "Unexpected end of input", "\"foo");
assertParseException(5, "Unexpected end of input", "\"foo\\");
assertParseException(6, "Unexpected end of input", "\"foo\\n");
assertParseException(6, "Unexpected end of input", "\"foo\\u");
assertParseException(7, "Unexpected end of input", "\"foo\\u0");
assertParseException(9, "Unexpected end of input", "\"foo\\u000");
assertParseException(10, "Unexpected end of input", "\"foo\\u0000");
}
@Test
public void numbersinteger() throws IOException {
assertEquals(new JsonNumber("0"), parse("0"));
assertEquals(new JsonNumber("-0"), parse("-0"));
assertEquals(new JsonNumber("1"), parse("1"));
assertEquals(new JsonNumber("-1"), parse("-1"));
assertEquals(new JsonNumber("23"), parse("23"));
assertEquals(new JsonNumber("-23"), parse("-23"));
assertEquals(new JsonNumber("1234567890"), parse("1234567890"));
assertEquals(new JsonNumber("123456789012345678901234567890"),
parse("123456789012345678901234567890"));
}
@Test
public void numbersminusZero() throws IOException {
// allowed by JSON, allowed by Java
JsonValue value = parse("-0");
assertEquals(0, value.asInt());
assertEquals(0L, value.asLong());
assertEquals(0f, value.asFloat(), 0);
assertEquals(0d, value.asDouble(), 0);
}
@Test
public void numbersdecimal() throws IOException {
assertEquals(new JsonNumber("0.23"), parse("0.23"));
assertEquals(new JsonNumber("-0.23"), parse("-0.23"));
assertEquals(new JsonNumber("1234567890.12345678901234567890"),
parse("1234567890.12345678901234567890"));
}
@Test
public void numberswithExponent() throws IOException {
assertEquals(new JsonNumber("0.1e9"), parse("0.1e9"));
assertEquals(new JsonNumber("0.1e9"), parse("0.1e9"));
assertEquals(new JsonNumber("0.1E9"), parse("0.1E9"));
assertEquals(new JsonNumber("-0.23e9"), parse("-0.23e9"));
assertEquals(new JsonNumber("0.23e9"), parse("0.23e9"));
assertEquals(new JsonNumber("0.23e+9"), parse("0.23e+9"));
assertEquals(new JsonNumber("0.23e-9"), parse("0.23e-9"));
}
@Test
public void numberswithInvalidFormat() {
assertParseException(0, "Expected value", "+1");
assertParseException(0, "Expected value", ".1");
assertParseException(1, "Unexpected character", "02");
assertParseException(2, "Unexpected character", "-02");
assertParseException(1, "Expected digit", "-x");
assertParseException(2, "Expected digit", "1.x");
assertParseException(2, "Expected digit", "1ex");
assertParseException(3, "Unexpected character", "1e1x");
}
@Test
public void numbersincomplete() {
assertParseException(1, "Unexpected end of input", "-");
assertParseException(2, "Unexpected end of input", "1.");
assertParseException(4, "Unexpected end of input", "1.0e");
assertParseException(5, "Unexpected end of input", "1.0e-");
}
@Test
public void nullcomplete() throws IOException {
assertEquals(JsonLiteral.NULL, parse("null"));
}
@Test
public void nullincomplete() {
assertParseException(1, "Unexpected end of input", "n");
assertParseException(2, "Unexpected end of input", "nu");
assertParseException(3, "Unexpected end of input", "nul");
}
@Test
public void nullwithIllegalCharacter() {
assertParseException(1, "Expected 'u'", "nx");
assertParseException(2, "Expected 'l'", "nux");
assertParseException(3, "Expected 'l'", "nulx");
assertParseException(4, "Unexpected character", "nullx");
}
@Test
public void truecomplete() throws IOException {
assertSame(JsonLiteral.TRUE, parse("true"));
}
@Test
public void trueincomplete() {
assertParseException(1, "Unexpected end of input", "t");
assertParseException(2, "Unexpected end of input", "tr");
assertParseException(3, "Unexpected end of input", "tru");
}
@Test
public void truewithIllegalCharacter() {
assertParseException(1, "Expected 'r'", "tx");
assertParseException(2, "Expected 'u'", "trx");
assertParseException(3, "Expected 'e'", "trux");
assertParseException(4, "Unexpected character", "truex");
}
@Test
public void falsecomplete() throws IOException {
assertSame(JsonLiteral.FALSE, parse("false"));
}
@Test
public void falseincomplete() {
assertParseException(1, "Unexpected end of input", "f");
assertParseException(2, "Unexpected end of input", "fa");
assertParseException(3, "Unexpected end of input", "fal");
assertParseException(4, "Unexpected end of input", "fals");
}
@Test
public void falsewithIllegalCharacter() {
assertParseException(1, "Expected 'a'", "fx");
assertParseException(2, "Expected 'l'", "fax");
assertParseException(3, "Expected 's'", "falx");
assertParseException(4, "Expected 'e'", "falsx");
assertParseException(5, "Unexpected character", "falsex");
}
private void assertParseException(int offset, String message, final String json) {
TestHandler handler = new TestHandler();
JsonReader<Object, Object> reader = new JsonReader<>(new StringReader(json), handler);
JsonException exception = Assertions.assertThrows(JsonException.class, () -> {
try {
reader.parse();
} catch (IOException e) {
// ignore
}
});
assertThat(exception.getMessage(), startsWith(message));
}
private static class TestHandler implements JsonHandler<Object, Object> {
StringBuilder log = new StringBuilder();
int sequence = 0;
@Override
public void startNull() {
record("startNull");
}
@Override
public void endNull() {
record("endNull");
}
@Override
public void startBoolean() {
record("startBoolean");
}
@Override
public void endBoolean(boolean value) {
record("endBoolean", value);
}
@Override
public void startString() {
record("startString");
}
@Override
public void endString(String string) {
record("endString", string);
}
@Override
public void startNumber() {
record("startNumber");
}
@Override
public void endNumber(String string) {
record("endNumber", string);
}
@Override
public Object startArray() {
record("startArray");
return "a" + ++sequence;
}
@Override
public void endArray(Object array) {
record("endArray", array);
}
@Override
public void startArrayValue(Object array) {
record("startArrayValue", array);
}
@Override
public void endArrayValue(Object array) {
record("endArrayValue", array);
}
@Override
public Object startObject() {
record("startObject");
return "o" + ++sequence;
}
@Override
public void endObject(Object object) {
record("endObject", object);
}
@Override
public void startObjectName(Object object) {
record("startObjectName", object);
}
@Override
public void endObjectName(Object object, String name) {
record("endObjectName", object, name);
}
@Override
public void startObjectValue(Object object, String name) {
record("startObjectValue", object, name);
}
@Override
public void endObjectValue(Object object, String name) {
record("endObjectValue", object, name);
}
private void record(String event, Object... args) {
log.append(event);
for (Object arg : args) {
log.append(' ').append(arg);
}
log.append(' ').append('\n');
}
String getLog() {
return log.toString();
}
}
}

View file

@ -1,106 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.StringWriter;
public class JsonStringTest {
private StringWriter stringWriter;
private JsonWriter jsonWriter;
@BeforeEach
public void setUp() {
stringWriter = new StringWriter();
jsonWriter = new JsonWriter(stringWriter);
}
@Test
public void constructorFailsWithNull() {
Assertions.assertThrows(NullPointerException.class, () -> {
new JsonString(null);
});
}
@Test
public void write() throws IOException {
new JsonString("foo").write(jsonWriter);
assertEquals("\"foo\"", stringWriter.toString());
}
@Test
public void writeEscapesStrings() throws IOException {
new JsonString("foo\\bar").write(jsonWriter);
assertEquals("\"foo\\\\bar\"", stringWriter.toString());
}
@Test
public void isString() {
assertTrue(new JsonString("foo").isString());
}
@Test
public void asString() {
assertEquals("foo", new JsonString("foo").asString());
}
@Test
public void equalsTrueForSameInstance() {
JsonString string = new JsonString("foo");
assertEquals(string, string);
}
@Test
public void equalsTrueForEqualStrings() {
assertEquals(new JsonString("foo"), new JsonString("foo"));
}
@Test
public void equalsFalseForDifferentStrings() {
assertNotEquals(new JsonString(""), new JsonString("foo"));
assertNotEquals(new JsonString("foo"), new JsonString("bar"));
}
@Test
public void equalsFalseForNull() {
assertNotEquals(null, new JsonString("foo"));
}
@Test
public void equalsFalseForSubclass() {
assertNotEquals(new JsonString("foo"), new JsonString("foo") {
});
}
@Test
public void hashCodeEqualsForEqualStrings() {
assertEquals(new JsonString("foo").hashCode(), new JsonString("foo").hashCode());
}
@Test
public void hashCodeDiffersForDifferentStrings() {
assertNotEquals(new JsonString("").hashCode(), new JsonString("foo").hashCode());
assertNotEquals(new JsonString("foo").hashCode(), new JsonString("bar").hashCode());
}
}

View file

@ -1,219 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
public class JsonTest {
@Test
public void literalConstants() {
assertTrue(JsonLiteral.NULL.isNull());
assertTrue(JsonLiteral.TRUE.isTrue());
assertTrue(JsonLiteral.FALSE.isFalse());
}
@Test
public void valueInt() {
assertEquals("0", Json.of(0).toString());
assertEquals("23", Json.of(23).toString());
assertEquals("-1", Json.of(-1).toString());
assertEquals("2147483647", Json.of(Integer.MAX_VALUE).toString());
assertEquals("-2147483648", Json.of(Integer.MIN_VALUE).toString());
}
@Test
public void valueLong() {
assertEquals("0", Json.of(0L).toString());
assertEquals("9223372036854775807", Json.of(Long.MAX_VALUE).toString());
assertEquals("-9223372036854775808", Json.of(Long.MIN_VALUE).toString());
}
@Test
public void valueFloat() {
assertEquals("23.5", Json.of(23.5f).toString());
assertEquals("-3.1416", Json.of(-3.1416f).toString());
assertEquals("1.23E-6", Json.of(0.00000123f).toString());
assertEquals("-1.23E7", Json.of(-12300000f).toString());
}
@Test
public void valueFloatCutsOffPointZero() {
assertEquals("0", Json.of(0f).toString());
assertEquals("-1", Json.of(-1f).toString());
assertEquals("10", Json.of(10f).toString());
}
@Test
public void valueFloatFailsWithInfinity() {
String message = "Infinite and NaN values not permitted in JSON";
Assertions.assertThrows(IllegalArgumentException.class,
() -> Json.of(Float.POSITIVE_INFINITY), message);
}
@Test
public void valuefloatfailsWithNaN() {
String message = "Infinite and NaN values not permitted in JSON";
Assertions.assertThrows(IllegalArgumentException.class,
() -> Json.of(Float.NaN), message);
}
@Test
public void valueDouble() {
assertEquals("23.5", Json.of(23.5d).toString());
assertEquals("3.1416", Json.of(3.1416d).toString());
assertEquals("1.23E-6", Json.of(0.00000123d).toString());
assertEquals("1.7976931348623157E308", Json.of(1.7976931348623157E308d).toString());
}
@Test
public void valueDoublecutsOffPointZero() {
assertEquals("0", Json.of(0d).toString());
assertEquals("-1", Json.of(-1d).toString());
assertEquals("10", Json.of(10d).toString());
}
@Test
public void valuedoublefailsWithInfinity() {
String message = "Infinite and NaN values not permitted in JSON";
Assertions.assertThrows(IllegalArgumentException.class,
() -> Json.of(Double.POSITIVE_INFINITY), message);
}
@Test
public void valuedoublefailsWithNaN() {
String message = "Infinite and NaN values not permitted in JSON";
Assertions.assertThrows(IllegalArgumentException.class,
() -> Json.of(Double.NaN), message);
}
@Test
public void valueboolean() {
assertSame(JsonLiteral.TRUE, Json.of(true));
assertSame(JsonLiteral.FALSE, Json.of(false));
}
@Test
public void valuestring() {
assertEquals("", Json.of("").asString());
assertEquals("Hello", Json.of("Hello").asString());
assertEquals("\"Hello\"", Json.of("\"Hello\"").asString());
}
@Test
public void valuestringtoleratesNull() {
assertSame(JsonLiteral.NULL, Json.of(null));
}
@Test
public void array() {
assertEquals(new JsonArray(), Json.array());
}
@Test
public void arrayint() {
assertEquals(new JsonArray().add(23), Json.array(23));
assertEquals(new JsonArray().add(23).add(42), Json.array(23, 42));
}
@Test
public void arrayintfailsWithNull() {
Assertions.assertThrows(NullPointerException.class, () -> Json.array((int[]) null));
}
@Test
public void arraylong() {
assertEquals(new JsonArray().add(23L), Json.array(23L));
assertEquals(new JsonArray().add(23L).add(42L), Json.array(23L, 42L));
}
@Test
public void arraylongfailsWithNull() {
Assertions.assertThrows(NullPointerException.class, () -> Json.array((long[]) null));
}
@Test
public void arrayfloat() {
assertEquals(new JsonArray().add(3.14f), Json.array(3.14f));
assertEquals(new JsonArray().add(3.14f).add(1.41f), Json.array(3.14f, 1.41f));
}
@Test
public void arrayfloatfailsWithNull() {
Assertions.assertThrows(NullPointerException.class, () -> Json.array((float[]) null));
}
@Test
public void arraydouble() {
assertEquals(new JsonArray().add(3.14d), Json.array(3.14d));
assertEquals(new JsonArray().add(3.14d).add(1.41d), Json.array(3.14d, 1.41d));
}
@Test
public void arraydoublefailsWithNull() {
Assertions.assertThrows(NullPointerException.class, () -> Json.array((double[]) null));
}
@Test
public void arrayboolean() {
assertEquals(new JsonArray().add(true), Json.array(true));
assertEquals(new JsonArray().add(true).add(false), Json.array(true, false));
}
@Test
public void arraybooleanfailsWithNull() {
Assertions.assertThrows(NullPointerException.class, () -> Json.array((boolean[]) null));
}
@Test
public void arraystring() {
assertEquals(new JsonArray().add("foo"), Json.array("foo"));
assertEquals(new JsonArray().add("foo").add("bar"), Json.array("foo", "bar"));
}
@Test
public void arraystringfailsWithNull() {
Assertions.assertThrows(NullPointerException.class, () -> Json.array((String[]) null));
}
@Test
public void object() {
assertEquals(new JsonObject(), Json.object());
}
@Test
public void parsestring() throws IOException {
assertEquals(Json.of(23), Json.parse("23"));
}
@Test
public void parsestringfailsWithNull() {
Assertions.assertThrows(NullPointerException.class, () -> Json.parse((String) null));
}
@Test
public void parseReader() throws IOException {
Reader reader = new StringReader("23");
assertEquals(Json.of(23), Json.parse(reader));
}
}

View file

@ -22,7 +22,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.xbib.marc.StreamMatcher.assertStream; import static org.xbib.marc.StreamMatcher.assertStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter; import java.io.StringWriter;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -43,8 +42,6 @@ import java.util.Comparator;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
public class MarcJsonWriterTest { public class MarcJsonWriterTest {

View file

@ -1,114 +0,0 @@
/**
* Copyright 2016-2022 Jörg Prante <joergprante@gmail.com>
*
* Licensed 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
*
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a>
*
* 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.
*/
package org.xbib.marc.json;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.mock;
import static org.xbib.marc.json.JsonWriterConfig.prettyPrint;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Locale;
public class PrettyPrintTest {
@Test
public void testIndentWithSpaces_emptyArray() throws IOException {
StringWriter sw = new StringWriter();
JsonWriter output = JsonWriterConfig.prettyPrint(2).createWriter(sw);
new JsonArray().write(output);
assertEquals("[\n \n]", sw.toString());
}
@Test
public void testIndentWithSpaces_emptyObject() throws IOException {
StringWriter sw = new StringWriter();
JsonWriter output = JsonWriterConfig.prettyPrint(2).createWriter(sw);
new JsonObject().write(output);
assertEquals("{\n \n}", sw.toString());
}
@Test
public void testIndentWithSpaces_array() throws IOException {
StringWriter sw = new StringWriter();
JsonWriter output = JsonWriterConfig.prettyPrint(2).createWriter(sw);
new JsonArray().add(23).add(42).write(output);
assertEquals("[\n 23,\n 42\n]", sw.toString());
}
@Test
public void testIndentWithSpaces_nestedArray() throws IOException {
StringWriter sw = new StringWriter();
JsonWriter output = JsonWriterConfig.prettyPrint(2).createWriter(sw);
new JsonArray().add(23).add(new JsonArray().add(42)).write(output);
assertEquals("[\n 23,\n [\n 42\n ]\n]", sw.toString());
}
@Test
public void testIndentWithSpaces_object() throws IOException {
StringWriter sw = new StringWriter();
JsonWriter output = JsonWriterConfig.prettyPrint(2).createWriter(sw);
new JsonObject().add("a", 23).add("b", 42).write(output);
assertEquals("{\n \"a\": 23,\n \"b\": 42\n}", sw.toString());
}
@Test
public void testIndentWithSpaces_nestedObject() throws IOException {
StringWriter sw = new StringWriter();
JsonWriter output = JsonWriterConfig.prettyPrint(2).createWriter(sw);
new JsonObject().add("a", 23).add("b", new JsonObject().add("c", 42)).write(output);
assertEquals("{\n \"a\": 23,\n \"b\": {\n \"c\": 42\n }\n}", sw.toString());
}
@Test
public void testIndentWithSpaces_zero() throws IOException {
StringWriter sw = new StringWriter();
JsonWriter output = JsonWriterConfig.prettyPrint(0).createWriter(sw);
new JsonArray().add(23).add(42).write(output);
assertEquals("[\n23,\n42\n]", sw.toString());
}
@Test
public void testIndentWithSpaces_one() throws IOException {
StringWriter sw = new StringWriter();
JsonWriter output = JsonWriterConfig.prettyPrint(1).createWriter(sw);
new JsonArray().add(23).add(42).write(output);
assertEquals("[\n 23,\n 42\n]", sw.toString());
}
@Test
public void testIndentWithSpaces_failsWithNegativeValues() {
try {
prettyPrint(-1);
fail();
} catch (IllegalArgumentException ex) {
assertTrue(ex.getMessage().toLowerCase(Locale.US).contains("negative"));
}
}
@Test
public void testIndentWithSpaces_createsIndependentInstances() {
Writer writer = mock(Writer.class);
JsonWriterConfig config = prettyPrint(1);
Object instance1 = config.createWriter(writer);
Object instance2 = config.createWriter(writer);
assertNotSame(instance1, instance2);
}
}

File diff suppressed because one or more lines are too long