Java tutorial
/* * Copyright (c) 2011-2019 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.Shareable; import io.vertx.core.shareddata.impl.ClusterSerializable; import java.time.Instant; import java.util.*; 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 <a href="http://json.org/">JSON</a> 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 <a href="http://rfc-editor.org/rfc/rfc7493.txt">RFC-7493</a> to support Temporal * data types as well as binary data. * * Please see the documentation for more information. * * @author <a href="http://tfox.org">Tim Fox</a> */ public class JsonArray implements Iterable<Object>, ClusterSerializable, Shareable { private List<Object> list; /** * Create an instance from a String of JSON, this string must be a valid array otherwise an exception will be thrown. * <p/> * 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); } } /** * Get the String at position {@code pos} in the array, * * @param pos the position in the array * @return the String, or null if a null value present * @throws java.lang.ClassCastException if the value cannot be converted to String */ public String getString(int pos) { Object val = list.get(pos); if (val == null) { return null; } if (val instanceof CharSequence) { return val.toString(); } else 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 Enum) { return ((Enum) val).name(); } throw new ClassCastException( "class " + val.getClass().getName() + " cannot be cast to class java.lang.String"); } /** * 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; } // 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 Instant at position {@code pos} in the array. * * JSON itself has no notion of a temporal types, this extension complies to the RFC-7493, so this method assumes * there is a String value and it contains an ISO 8601 encoded date and time format such as "2017-04-03T10:25:41Z", * which it decodes if found and returns. * * @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. * <ul> * <li>{@code Map} will be wrapped to {@code JsonObject}</li> * <li>{@code List} will be wrapped to {@code JsonArray}</li> * <li>{@code Instant} will be converted to {@code String}</li> * <li>{@code byte[]} will be converted to {@code String}</li> * <li>{@code Enum} will be converted to {@code String}</li> * </ul> * * @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 unerlying List * * @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<Object> 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); } /** * Make a copy of the JSON array * * @return a copy */ @Override public JsonArray copy() { List<Object> copiedList = new ArrayList<>(list.size()); for (Object val : list) { copiedList.add(checkAndCopy(val)); } return new JsonArray(copiedList); } /** * Get a Stream over the entries in the JSON array * * @return a Stream */ public Stream<Object> stream() { return JsonObject.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) { String encoded = encode(); byte[] bytes = encoded.getBytes(); buffer.appendInt(bytes.length); buffer.appendBytes(bytes); } @Override public int readFromBuffer(int pos, Buffer buffer) { int length = buffer.getInt(pos); int start = pos + 4; String encoded = buffer.getString(start, start + length); fromJson(encoded); 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<Object> { final Iterator<Object> listIter; Iter(Iterator<Object> 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(); } } }