Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.vertx.core.json.JsonArray Maven / Gradle / Ivy
/*
* Copyright (c) 2011-2022 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*/
package io.vertx.core.json;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.shareddata.ClusterSerializable;
import io.vertx.core.shareddata.Shareable;
import java.time.Instant;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;
import static io.vertx.core.json.impl.JsonUtil.*;
import static java.time.format.DateTimeFormatter.ISO_INSTANT;
/**
* A representation of a JSON array in Java.
*
* Unlike some other languages Java does not have a native understanding of JSON. To enable JSON to be used easily
* in Vert.x code we use this class to encapsulate the notion of a JSON array.
*
* The implementation adheres to the RFC-7493 to support Temporal
* data types as well as binary data.
*
* Please see the documentation for more information.
*
* @author Tim Fox
*/
public class JsonArray implements Iterable, ClusterSerializable, Shareable {
private List list;
/**
* Create an instance from a String of JSON, this string must be a valid array otherwise an exception will be thrown.
*
* If you are unsure of the value, you should use instead {@link Json#decodeValue(String)} and check the result is
* a JSON array.
*
* @param json the string of JSON
*/
public JsonArray(String json) {
if (json == null) {
throw new NullPointerException();
}
fromJson(json);
if (list == null) {
throw new DecodeException("Invalid JSON array: " + json);
}
}
/**
* Create an empty instance
*/
public JsonArray() {
list = new ArrayList<>();
}
/**
* Create an instance from a List. The List is not copied.
*
* @param list the underlying backing list
*/
public JsonArray(List list) {
if (list == null) {
throw new NullPointerException();
}
this.list = list;
}
/**
* Create an instance from a Buffer of JSON.
*
* @param buf the buffer of JSON.
*/
public JsonArray(Buffer buf) {
if (buf == null) {
throw new NullPointerException();
}
fromBuffer(buf);
if (list == null) {
throw new DecodeException("Invalid JSON array: " + buf);
}
}
/**
* Create a JsonArray containing an arbitrary number of values.
*
* @param values The objects into JsonArray.
* @throws NullPointerException if the args is null.
*/
public static JsonArray of(Object... values) {
// implicit nullcheck of values
if (values.length == 0) {
return new JsonArray();
}
JsonArray arr = new JsonArray(new ArrayList<>(values.length));
for(int i = 0; i< values.length; ++i) {
arr.add(values[i]);
}
return arr;
}
/**
* Get the String at position {@code pos} in the array,
*
* @param pos the position in the array
* @return the String (or String representation), or null if a null value present
*/
public String getString(int pos) {
Object val = list.get(pos);
if (val == null) {
return null;
}
if (val instanceof Instant) {
return ISO_INSTANT.format((Instant) val);
} else if (val instanceof byte[]) {
return BASE64_ENCODER.encodeToString((byte[]) val);
} else if (val instanceof Buffer) {
return BASE64_ENCODER.encodeToString(((Buffer) val).getBytes());
} else if (val instanceof Enum) {
return ((Enum) val).name();
} else {
return val.toString();
}
}
/**
* Get the Number at position {@code pos} in the array,
*
* @param pos the position in the array
* @return the Number, or null if a null value present
* @throws java.lang.ClassCastException if the value is not a Number
*/
public Number getNumber(int pos) {
return (Number) list.get(pos);
}
/**
* Get the Integer at position {@code pos} in the array,
*
* @param pos the position in the array
* @return the Integer, or null if a null value present
* @throws java.lang.ClassCastException if the value cannot be converted to Integer
*/
public Integer getInteger(int pos) {
Number number = (Number) list.get(pos);
if (number == null) {
return null;
} else if (number instanceof Integer) {
return (Integer) number; // Avoids unnecessary unbox/box
} else {
return number.intValue();
}
}
/**
* Get the Long at position {@code pos} in the array,
*
* @param pos the position in the array
* @return the Long, or null if a null value present
* @throws java.lang.ClassCastException if the value cannot be converted to Long
*/
public Long getLong(int pos) {
Number number = (Number) list.get(pos);
if (number == null) {
return null;
} else if (number instanceof Long) {
return (Long) number; // Avoids unnecessary unbox/box
} else {
return number.longValue();
}
}
/**
* Get the Double at position {@code pos} in the array,
*
* @param pos the position in the array
* @return the Double, or null if a null value present
* @throws java.lang.ClassCastException if the value cannot be converted to Double
*/
public Double getDouble(int pos) {
Number number = (Number) list.get(pos);
if (number == null) {
return null;
} else if (number instanceof Double) {
return (Double) number; // Avoids unnecessary unbox/box
} else {
return number.doubleValue();
}
}
/**
* Get the Float at position {@code pos} in the array,
*
* @param pos the position in the array
* @return the Float, or null if a null value present
* @throws java.lang.ClassCastException if the value cannot be converted to Float
*/
public Float getFloat(int pos) {
Number number = (Number) list.get(pos);
if (number == null) {
return null;
} else if (number instanceof Float) {
return (Float) number; // Avoids unnecessary unbox/box
} else {
return number.floatValue();
}
}
/**
* Get the Boolean at position {@code pos} in the array,
*
* @param pos the position in the array
* @return the Boolean, or null if a null value present
* @throws java.lang.ClassCastException if the value cannot be converted to Integer
*/
public Boolean getBoolean(int pos) {
return (Boolean) list.get(pos);
}
/**
* Get the JsonObject at position {@code pos} in the array.
*
* @param pos the position in the array
* @return the JsonObject, or null if a null value present
* @throws java.lang.ClassCastException if the value cannot be converted to JsonObject
*/
public JsonObject getJsonObject(int pos) {
Object val = list.get(pos);
if (val instanceof Map) {
val = new JsonObject((Map) val);
}
return (JsonObject) val;
}
/**
* Get the JsonArray at position {@code pos} in the array.
*
* @param pos the position in the array
* @return the Integer, or null if a null value present
* @throws java.lang.ClassCastException if the value cannot be converted to JsonArray
*/
public JsonArray getJsonArray(int pos) {
Object val = list.get(pos);
if (val instanceof List) {
val = new JsonArray((List) val);
}
return (JsonArray) val;
}
/**
* Get the byte[] at position {@code pos} in the array.
*
* JSON itself has no notion of a binary, so this method assumes there is a String value and
* it contains a Base64 encoded binary, which it decodes if found and returns.
*
* @param pos the position in the array
* @return the byte[], or null if a null value present
* @throws java.lang.ClassCastException if the value cannot be converted to String
* @throws java.lang.IllegalArgumentException if the String value is not a legal Base64 encoded value
*/
public byte[] getBinary(int pos) {
Object val = list.get(pos);
// no-op
if (val == null) {
return null;
}
// no-op if value is already an byte[]
if (val instanceof byte[]) {
return (byte[]) val;
}
// unwrap if value is already a Buffer
if (val instanceof Buffer) {
return ((Buffer) val).getBytes();
}
// assume that the value is in String format as per RFC
String encoded = (String) val;
// parse to proper type
return BASE64_DECODER.decode(encoded);
}
/**
* Get the Buffer at position {@code pos} in the array.
*
* JSON itself has no notion of a binary, so this method assumes there is a String value and
* it contains a Base64 encoded binary, which it decodes if found and returns.
*
* @param pos the position in the array
* @return the byte[], or null if a null value present
* @throws java.lang.ClassCastException if the value cannot be converted to String
* @throws java.lang.IllegalArgumentException if the String value is not a legal Base64 encoded value
*/
public Buffer getBuffer(int pos) {
Object val = list.get(pos);
// no-op
if (val == null) {
return null;
}
// no-op if value is already an Buffer
if (val instanceof Buffer) {
return (Buffer) val;
}
// wrap if value is already a byte[]
if (val instanceof byte[]) {
return Buffer.buffer((byte[]) val);
}
// assume that the value is in String format as per RFC
String encoded = (String) val;
// parse to proper type
return Buffer.buffer(BASE64_DECODER.decode(encoded));
}
/**
* Get the Instant at position {@code pos} in the array.
*
* JSON itself has no notion of a temporal types, this extension allows ISO 8601 string formatted dates with timezone
* always set to zero UTC offset, as denoted by the suffix "Z" to be parsed as a instant value.
* {@code YYYY-MM-DDTHH:mm:ss.sssZ} is the default format used by web browser scripting. This extension complies to
* the RFC-7493 with all the restrictions mentioned before. The method will then decode and return a instant value.
*
* @param pos the position in the array
* @return the Instant, or null if a null value present
* @throws java.lang.ClassCastException if the value cannot be converted to String
* @throws java.time.format.DateTimeParseException if the String value is not a legal ISO 8601 encoded value
*/
public Instant getInstant(int pos) {
Object val = list.get(pos);
// no-op
if (val == null) {
return null;
}
// no-op if value is already an Instant
if (val instanceof Instant) {
return (Instant) val;
}
// assume that the value is in String format as per RFC
String encoded = (String) val;
// parse to proper type
return Instant.from(ISO_INSTANT.parse(encoded));
}
/**
* Get the value with the specified key, as an Object with types respecting the limitations of JSON.
*
* {@code Map} will be wrapped to {@code JsonObject}
* {@code List} will be wrapped to {@code JsonArray}
* {@code Instant} will be converted to {@code String}
* {@code byte[]} will be converted to {@code String}
* {@code Buffer} will be converted to {@code String}
* {@code Enum} will be converted to {@code String}
*
*
* @param pos the position in the array
* @return the Integer, or null if a null value present
*/
public Object getValue(int pos) {
return wrapJsonValue(list.get(pos));
}
/**
* Is there a null value at position pos?
*
* @param pos the position in the array
* @return true if null value present, false otherwise
*/
public boolean hasNull(int pos) {
return list.get(pos) == null;
}
/**
* Add a null value to the JSON array.
*
* @return a reference to this, so the API can be used fluently
*/
public JsonArray addNull() {
list.add(null);
return this;
}
/**
* Add an Object to the JSON array.
*
* @param value the value
* @return a reference to this, so the API can be used fluently
*/
public JsonArray add(Object value) {
list.add(value);
return this;
}
/**
* Add an Object to the JSON array at given position {@code pos}.
*
* @param pos the position
* @param value the value
* @return a reference to this, so the API can be used fluently
*/
public JsonArray add(int pos, Object value) {
list.add(pos, value);
return this;
}
/**
* Appends all of the elements in the specified array to the end of this JSON array.
*
* @param array the array
* @return a reference to this, so the API can be used fluently
*/
public JsonArray addAll(JsonArray array) {
list.addAll(array.list);
return this;
}
/**
* Set a null value to the JSON array at position {@code pos}.
*
* @return a reference to this, so the API can be used fluently
*/
public JsonArray setNull(int pos) {
list.set(pos, null);
return this;
}
/**
* Set an Object to the JSON array at position {@code pos}.
*
* @param pos position in the array
* @param value the value
* @return a reference to this, so the API can be used fluently
*/
public JsonArray set(int pos, Object value) {
list.set(pos, value);
return this;
}
/**
* Does the JSON array contain the specified value? This method will scan the entire array until it finds a value
* or reaches the end.
*
* @param value the value
* @return true if it contains the value, false if not
*/
public boolean contains(Object value) {
return list.contains(value);
}
/**
* Remove the specified value from the JSON array. This method will scan the entire array until it finds a value
* or reaches the end.
*
* @param value the value to remove
* @return true if it removed it, false if not found
*/
public boolean remove(Object value) {
final Object wrappedValue = wrapJsonValue(value);
for (int i = 0; i < list.size(); i++) {
// perform comparision on wrapped types
final Object otherWrapperValue = getValue(i);
if (wrappedValue == null) {
if (otherWrapperValue == null) {
list.remove(i);
return true;
}
} else {
if (wrappedValue.equals(otherWrapperValue)) {
list.remove(i);
return true;
}
}
}
return false;
}
/**
* Remove the value at the specified position in the JSON array.
*
* @param pos the position to remove the value at
* @return the removed value if removed, null otherwise. If the value is a Map, a {@link JsonObject} is built from
* this Map and returned. It the value is a List, a {@link JsonArray} is built form this List and returned.
*/
public Object remove(int pos) {
return wrapJsonValue(list.remove(pos));
}
/**
* Get the number of values in this JSON array
*
* @return the number of items
*/
public int size() {
return list.size();
}
/**
* Are there zero items in this JSON array?
*
* @return true if zero, false otherwise
*/
public boolean isEmpty() {
return list.isEmpty();
}
/**
* Get the underlying {@code List} as is.
*
* This list may contain values that are not the types returned by the {@code JsonArray} and
* with an unpredictable representation of the value, e.g you might get a JSON object
* as a {@link JsonObject} or as a {@link Map}.
*
* @return the underlying List.
*/
public List getList() {
return list;
}
/**
* Remove all entries from the JSON array
*
* @return a reference to this, so the API can be used fluently
*/
public JsonArray clear() {
list.clear();
return this;
}
/**
* Get an Iterator over the values in the JSON array
*
* @return an iterator
*/
@Override
public Iterator iterator() {
return new Iter(list.iterator());
}
/**
* Encode the JSON array to a string
*
* @return the string encoding
*/
public String encode() {
return Json.CODEC.toString(this, false);
}
/**
* Encode this JSON object as buffer.
*
* @return the buffer encoding.
*/
public Buffer toBuffer() {
return Json.CODEC.toBuffer(this, false);
}
/**
* Encode the JSON array prettily as a string
*
* @return the string encoding
*/
public String encodePrettily() {
return Json.CODEC.toString(this, true);
}
/**
* Deep copy of the JSON array.
*
* @return a copy where all elements have been copied recursively
* @throws IllegalStateException when a nested element cannot be copied
*/
@Override
public JsonArray copy() {
return copy(DEFAULT_CLONER);
}
/**
* Deep copy of the JSON array.
*
* Unlike {@link #copy()} that can fail when an unknown element cannot be copied, this method
* delegates the copy of such element to the {@code cloner} function and will not fail.
*
* @param cloner a function that copies custom values not supported by the JSON implementation
* @return a copy where all elements have been copied recursively
*/
public JsonArray copy(Function cloner) {
List copiedList = new ArrayList<>(list.size());
for (Object val : list) {
copiedList.add(deepCopy(val, cloner));
}
return new JsonArray(copiedList);
}
/**
* Get a Stream over the entries in the JSON array. The values in the stream will follow
* the same rules as defined in {@link #getValue(int)}, respecting the JSON requirements.
*
* To stream the raw values, use the storage object stream instead:
* {@code
* jsonArray
* .getList()
* .stream()
* }
*
* @return a Stream
*/
public Stream stream() {
return asStream(iterator());
}
@Override
public String toString() {
return encode();
}
@Override
public boolean equals(Object o) {
// null check
if (o == null)
return false;
// self check
if (this == o)
return true;
// type check and cast
if (getClass() != o.getClass())
return false;
JsonArray other = (JsonArray) o;
// size check
if (this.size() != other.size())
return false;
// value comparison
for (int i = 0; i < this.size(); i++) {
Object thisValue = this.getValue(i);
Object otherValue = other.getValue(i);
// identity check
if (thisValue == otherValue) {
continue;
}
// special case for numbers
if (thisValue instanceof Number && otherValue instanceof Number && thisValue.getClass() != otherValue.getClass()) {
Number n1 = (Number) thisValue;
Number n2 = (Number) otherValue;
// floating point values
if (thisValue instanceof Float || thisValue instanceof Double || otherValue instanceof Float || otherValue instanceof Double) {
// compare as floating point double
if (n1.doubleValue() == n2.doubleValue()) {
// same value check the next entry
continue;
}
}
if (thisValue instanceof Integer || thisValue instanceof Long || otherValue instanceof Integer || otherValue instanceof Long) {
// compare as integer long
if (n1.longValue() == n2.longValue()) {
// same value check the next entry
continue;
}
}
}
// special case for char sequences
if (thisValue instanceof CharSequence && otherValue instanceof CharSequence && thisValue.getClass() != otherValue.getClass()) {
CharSequence s1 = (CharSequence) thisValue;
CharSequence s2 = (CharSequence) otherValue;
if (Objects.equals(s1.toString(), s2.toString())) {
// same value check the next entry
continue;
}
}
// fallback to standard object equals checks
if (!Objects.equals(thisValue, otherValue)) {
return false;
}
}
// all checks passed
return true;
}
@Override
public int hashCode() {
return list.hashCode();
}
@Override
public void writeToBuffer(Buffer buffer) {
Buffer buf = toBuffer();
buffer.appendInt(buf.length());
buffer.appendBuffer(buf);
}
@Override
public int readFromBuffer(int pos, Buffer buffer) {
int length = buffer.getInt(pos);
int start = pos + 4;
Buffer buf = buffer.getBuffer(start, start + length);
fromBuffer(buf);
return pos + length + 4;
}
private void fromJson(String json) {
list = Json.CODEC.fromString(json, List.class);
}
private void fromBuffer(Buffer buf) {
list = Json.CODEC.fromBuffer(buf, List.class);
}
private static class Iter implements Iterator {
final Iterator listIter;
Iter(Iterator listIter) {
this.listIter = listIter;
}
@Override
public boolean hasNext() {
return listIter.hasNext();
}
@Override
public Object next() {
return wrapJsonValue(listIter.next());
}
@Override
public void remove() {
listIter.remove();
}
}
}