diff --git a/json-core/.gitignore b/json-core/.gitignore index 796b96d..c4dfdc5 100644 --- a/json-core/.gitignore +++ b/json-core/.gitignore @@ -1 +1,2 @@ /build +/target/ diff --git a/json-core/src/main/java/io/apptik/json/AbstractValidator.java b/json-core/src/main/java/io/apptik/json/AbstractValidator.java index 1ed2cb3..dffc620 100644 --- a/json-core/src/main/java/io/apptik/json/AbstractValidator.java +++ b/json-core/src/main/java/io/apptik/json/AbstractValidator.java @@ -1,33 +1,30 @@ package io.apptik.json; - public abstract class AbstractValidator implements Validator { - @Override - public boolean isValid(JsonElement el) { - //System.out.println("Started simple JsonSchema Validation using: " + this.getTitle()); - return doValidate(el, null); - } - - @Override - public String validate(JsonElement el) { - //System.out.println("Started JsonSchema Validation using: " + this.getTitle()); - StringBuilder sb = new StringBuilder(); - doValidate(el, sb); - return sb.toString(); - } - - @Override - public boolean validate(JsonElement el, StringBuilder sb) { - //System.out.println("Started full JsonSchema Validation using: " + this.getTitle()); - return doValidate(el, sb); - } - - protected abstract boolean doValidate(JsonElement el, StringBuilder sb); - - @Override - public String getTitle() { - return getClass().getCanonicalName(); - } + protected abstract boolean doValidate(JsonElement el, StringBuilder sb); + + public String getTitle() { + return getClass().getCanonicalName(); + } + + public boolean isValid(final JsonElement el) { + // System.out.println("Started simple JsonSchema Validation using: " + + // this.getTitle()); + return doValidate(el, null); + } + + public String validate(final JsonElement el) { + // System.out.println("Started JsonSchema Validation using: " + + // this.getTitle()); + StringBuilder sb = new StringBuilder(); + doValidate(el, sb); + return sb.toString(); + } + + public boolean validate(final JsonElement el, final StringBuilder sb) { + // System.out.println("Started full JsonSchema Validation using: " + + // this.getTitle()); + return doValidate(el, sb); + } } - diff --git a/json-core/src/main/java/io/apptik/json/JsonArray.java b/json-core/src/main/java/io/apptik/json/JsonArray.java index 3be5723..73fb98c 100644 --- a/json-core/src/main/java/io/apptik/json/JsonArray.java +++ b/json-core/src/main/java/io/apptik/json/JsonArray.java @@ -17,6 +17,11 @@ package io.apptik.json; +import static io.apptik.json.JsonNull.JSON_NULL; +import io.apptik.json.exception.JsonException; +import io.apptik.json.util.Freezable; +import io.apptik.json.util.Util; + import java.io.IOException; import java.lang.reflect.Array; import java.util.ArrayList; @@ -25,834 +30,848 @@ import java.util.List; import java.util.ListIterator; -import io.apptik.json.exception.JsonException; -import io.apptik.json.util.Freezable; -import io.apptik.json.util.Util; - -import static io.apptik.json.JsonNull.JSON_NULL; - // Note: this class was written without inspecting the non-free org.json sourcecode. /** * A dense indexed sequence of values. Values may be any mix of * {@link JsonObject JsonObjects}, other {@link JsonArray JsonArrays}, Strings, - * Booleans, Integers, Longs, Doubles, {@code null} or {@link JsonNull}. - * Values may not be {@link Double#isNaN() NaNs}, {@link Double#isInfinite() + * Booleans, Integers, Longs, Doubles, {@code null} or {@link JsonNull}. Values + * may not be {@link Double#isNaN() NaNs}, {@link Double#isInfinite() * infinities}, or of any type not listed here. *

- *

{@code JsonArray} has the same type coercion behavior and - * optional/mandatory accessors as {@link JsonObject}. See that class' - * documentation for details. *

- *

Warning: this class represents null in two incompatible - * ways: the standard Java {@code null} reference, and the sentinel value {@link - * JsonNull}. In particular, {@code get} fails if the requested index + * {@code JsonArray} has the same type coercion behavior and optional/mandatory + * accessors as {@link JsonObject}. See that class' documentation for details. + *

+ *

+ * Warning: this class represents null in two incompatible + * ways: the standard Java {@code null} reference, and the sentinel value + * {@link JsonNull}. In particular, {@code get} fails if the requested index * holds the null reference, but succeeds if it holds {@code JsonNull}. *

- *

Instances of this class are not thread safe. + *

+ * Instances of this class are not thread safe. */ -public final class JsonArray extends JsonElement implements List, Freezable { - - private volatile boolean frozen = false; - private final List values; - - /** - * Creates a {@code JsonArray} with no values. - */ - public JsonArray() { - values = new ArrayList(); - } - - /** - * Creates a new {@code JsonArray} by copying all values from the given - * collection. - * - * @param copyFrom a collection whose values are of supported types. - * Unsupported values are not permitted and will yield an array in an - * inconsistent state. - */ - /* Accept a raw type for API compatibility */ - public JsonArray(Collection copyFrom) throws JsonException { - this(); - if (copyFrom != null) { - for (Object aCopyFrom : copyFrom) { - put(JsonElement.wrap(aCopyFrom)); - } - } - } - - - /** - * Creates a new {@code JsonArray} with values from the given primitive array. - */ - public JsonArray(Object array) throws JsonException { - if (!array.getClass().isArray()) { - throw new JsonException("Not a primitive array: " + array.getClass()); - } - final int length = Array.getLength(array); - values = new ArrayList(length); - for (int i = 0; i < length; ++i) { - put(JsonElement.wrap(Array.get(array, i))); - } - } - - /** - * Returns the number of values in this array. - */ - public int length() { - return values.size(); - } - - /** - * Appends {@code value} to the end of this array. - * - * @return this array. - */ - public JsonArray put(boolean value) { - return put(new JsonBoolean(value)); - } - - /** - * Appends {@code value} to the end of this array. - * - * @param value a finite value. May not be {@link Double#isNaN() NaNs} or - * {@link Double#isInfinite() infinities}. - * @return this array. - */ - public JsonArray put(double value) throws JsonException { - return put(new JsonNumber(value)); - } - - /** - * Appends {@code value} to the end of this array. - * - * @return this array. - */ - public JsonArray put(int value) { - return put(new JsonNumber(value)); - } - - /** - * Appends {@code value} to the end of this array. - * - * @return this array. - */ - public JsonArray put(long value) { - return put(new JsonNumber(value)); - } - - /** - * Appends {@code value} to the end of this array. - * - * @param value a {@link JsonObject}, {@link JsonArray}, String, Boolean, - * Integer, Long, Double, or {@code null}. May - * not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite() - * infinities}. Unsupported values are not permitted and will cause the - * array to be in an inconsistent state. - * @return this array. - */ - public JsonArray put(Object value) throws JsonException { - return put(wrap(value)); - } - - public JsonArray put(JsonElement value) { - checkIfFrozen(); - putInternal(value); - - return this; - } - - /** - * Sets the value at {@code index} to {@code value}, null padding this array - * to the required length if necessary. If a value already exists at {@code - * index}, it will be replaced. - * - * @return this array. - */ - public JsonArray put(int index, boolean value) throws JsonException { - return put(index, (Boolean) value); - } - - /** - * Sets the value at {@code index} to {@code value}, null padding this array - * to the required length if necessary. If a value already exists at {@code - * index}, it will be replaced. - * - * @param value a finite value. May not be {@link Double#isNaN() NaNs} or - * {@link Double#isInfinite() infinities}. - * @return this array. - */ - public JsonArray put(int index, double value) throws JsonException { - return put(index, (Double) value); - } - - /** - * Sets the value at {@code index} to {@code value}, null padding this array - * to the required length if necessary. If a value already exists at {@code - * index}, it will be replaced. - * - * @return this array. - */ - public JsonArray put(int index, int value) throws JsonException { - return put(index, (Integer) value); - } - - /** - * Sets the value at {@code index} to {@code value}, null padding this array - * to the required length if necessary. If a value already exists at {@code - * index}, it will be replaced. - * - * @return this array. - */ - public JsonArray put(int index, long value) throws JsonException { - return put(index, (Long) value); - } - - /** - * Sets the value at {@code index} to {@code value}, null padding this array - * to the required length if necessary. If a value already exists at {@code - * index}, it will be replaced. - * - * @param value a {@link JsonObject}, {@link JsonArray}, String, Boolean, - * Integer, Long, Double, or {@code null}. May - * not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite() - * infinities}. - * @return this array. - */ - public JsonArray put(int index, Object value) throws JsonException { - checkIfFrozen(); - while (values.size() <= index) { - values.add(JSON_NULL); - } - values.set(index, wrap(value)); - return this; - } - - /** - * Returns true if this array has no value at {@code index}, or if its value - * is the {@code null} reference . - */ - public boolean isNull(int index) { - Object value = opt(index); - return value == null || value.equals(JSON_NULL); - } - - /** - * Returns the value at {@code index}. - * - * @throws JsonException if this array has no value at {@code index}, or if - * that value is the {@code null} reference. This method returns - * normally if the value is {@code JsonObject#NULL}. - */ - public JsonElement get(int index) throws JsonException { - try { - JsonElement value = values.get(index); - if (value == null) { - throw new JsonException("Value at " + index + " is null."); - } - return value; - } catch (IndexOutOfBoundsException e) { - throw new JsonException("Index " + index + " out of range [0.." + values.size() + ")"); - } - } - - @Override - public JsonElement set(int i, JsonElement jsonElement) { - checkIfFrozen(); - return values.set(i, jsonElement); - } - - @Override - public void add(int i, JsonElement jsonElement) { - checkIfFrozen(); - put(i, jsonElement); - } - - /** - * Returns the value at {@code index}, or null if the array has no value - * at {@code index}. - */ - public JsonElement opt(int index) { - if (index < 0 || index >= values.size()) { - return null; - } - return values.get(index); - } - - /** - * Removes and returns the value at {@code index}, or null if the array has no value - * at {@code index}. - */ - public JsonElement remove(int index) { - checkIfFrozen(); - if (index < 0 || index >= values.size()) { - return null; - } - return values.remove(index); - } - - @Override - public int indexOf(Object o) { - return values.indexOf(o); - } - - @Override - public int lastIndexOf(Object o) { - return values.lastIndexOf(o); - } - - @Override - public ListIterator listIterator() { - return values.listIterator(); - } - - @Override - public ListIterator listIterator(int i) { - return values.listIterator(i); - } - - @Override - public List subList(int i, int i2) { - return values.subList(i, i2); - } - - /** - * Returns the value at {@code index} if it exists and is a boolean or can - * be coerced to a boolean. - * - * @throws JsonException if the value at {@code index} doesn't exist or - * cannot be coerced to a boolean. - */ - public Boolean getBoolean(int index, boolean strict) throws JsonException { - JsonElement el = get(index); - Boolean res = null; - if (strict && !el.isBoolean()) { - throw Util.typeMismatch(index, el, "boolean", true); - } - if (el.isBoolean()) { - res = el.asBoolean(); - } - if (el.isString()) { - res = Util.toBoolean(el.asString()); - } - if (res == null) - throw Util.typeMismatch(index, el, "boolean", strict); - return res; - } - - public Boolean getBoolean(int index) throws JsonException { - return getBoolean(index, false); - } - - /** - * Returns the value at {@code index} if it exists and is a boolean or can - * be coerced to a boolean. Returns false otherwise. - */ - public Boolean optBoolean(int index) { - return optBoolean(index, null); - } - - /** - * Returns the value at {@code index} if it exists and is a boolean or can - * be coerced to a boolean. Returns {@code fallback} otherwise. - */ - public Boolean optBoolean(int index, Boolean fallback) { - return optBoolean(index, fallback, false); - } - - public Boolean optBoolean(int index, Boolean fallback, boolean strict) { - try { - return getBoolean(index, strict); - } catch (JsonException e) { - return fallback; - } - } - - /** - * Returns the value at {@code index} if it exists and is a double or can - * be coerced to a double. - * - * @throws JsonException if the value at {@code index} doesn't exist or - * cannot be coerced to a double. - */ - public Double getDouble(int index, boolean strict) throws JsonException { - JsonElement el = get(index); - Double res = null; - if (strict && !el.isNumber()) { - throw Util.typeMismatch(index, el, "double", true); - } - if (el.isNumber()) { - res = el.asDouble(); - } - if (el.isString()) { - res = Util.toDouble(el.asString()); - } - if (res == null) - throw Util.typeMismatch(index, el, "double", strict); - return res; - } - - public Double getDouble(int index) throws JsonException { - return getDouble(index, false); - } - - /** - * Returns the value at {@code index} if it exists and is a double or can - * be coerced to a double. Returns {@code NaN} otherwise. - */ - public Double optDouble(int index) { - return optDouble(index, null); - } - - /** - * Returns the value at {@code index} if it exists and is a double or can - * be coerced to a double. Returns {@code fallback} otherwise. - */ - public Double optDouble(int index, Double fallback) { - return optDouble(index, fallback, false); - } - - public Double optDouble(int index, Double fallback, boolean strict) { - try { - return getDouble(index, strict); - } catch (JsonException e) { - return fallback; - } - } - - /** - * Returns the value at {@code index} if it exists and is an int or - * can be coerced to an int. - * - * @throws JsonException if the value at {@code index} doesn't exist or - * cannot be coerced to a int. - */ - public Integer getInt(int index) throws JsonException { - return getInt(index, false); - } - - /** - * Returns the value at {@code index} if it exists and is an int or - * can be coerced to an int. Returns 0 otherwise. - */ - public Integer getInt(int index, boolean strict) throws JsonException { - JsonElement el = get(index); - Integer res = null; - if (strict && !el.isNumber()) { - throw Util.typeMismatch(index, el, "int", true); - } - if (el.isNumber()) { - res = el.asInt(); - } - if (el.isString()) { - res = Util.toInteger(el.asString()); - } - if (res == null) - throw Util.typeMismatch(index, el, "int", strict); - return res; - } - - - public Integer optInt(int index) { - return optInt(index, null); - } - - /** - * Returns the value at {@code index} if it exists and is an int or - * can be coerced to an int. Returns {@code fallback} otherwise. - */ - public Integer optInt(int index, Integer fallback) { - return optInt(index, fallback, false); - } - - public Integer optInt(int index, Integer fallback, boolean strict) { - try { - return getInt(index, strict); - } catch (JsonException e) { - return fallback; - } - } - - /** - * Returns the value at {@code index} if it exists and is a long or - * can be coerced to a long. - * - * @throws JsonException if the value at {@code index} doesn't exist or - * cannot be coerced to a long. - */ - - public Long getLong(int index, boolean strict) throws JsonException { - JsonElement el = get(index); - Long res = null; - if (strict && !el.isNumber()) { - throw Util.typeMismatch(index, el, "long", true); - } - if (el.isNumber()) { - res = el.asLong(); - } - if (el.isString()) { - res = Util.toLong(el.asString()); - } - if (res == null) - throw Util.typeMismatch(index, el, "long", strict); - return res; - } - - public Long getLong(int index) throws JsonException { - return getLong(index, false); - } - - /** - * Returns the value at {@code index} if it exists and is a long or - * can be coerced to a long. Returns 0 otherwise. - */ - public Long optLong(int index) { - return optLong(index, null); - } - - /** - * Returns the value at {@code index} if it exists and is a long or - * can be coerced to a long. Returns {@code fallback} otherwise. - */ - public Long optLong(int index, Long fallback) { - return optLong(index, fallback, false); - } - - - public Long optLong(int index, Long fallback, boolean strict) { - try { - return getLong(index, strict); - } catch (JsonException e) { - return fallback; - } - } - - /** - * Returns the value at {@code index} if it exists, coercing it if - * necessary. - * - * @throws JsonException if no such value exists. - */ - public String getString(int index, boolean strict) throws JsonException { - JsonElement el = get(index); - String res = null; - if (strict && !el.isString()) { - throw Util.typeMismatch(index, el, "string", true); - } - res = el.toString(); - if (res == null) - throw Util.typeMismatch(index, el, "string", strict); - return res; - } - - public String getString(int index) throws JsonException { - return getString(index, false); - } - - /** - * Returns the value at {@code index} if it exists, coercing it if - * necessary. Returns the empty string if no such value exists. - */ - public String optString(int index) { - return optString(index, null); - } - - /** - * Returns the value at {@code index} if it exists, coercing it if - * necessary. Returns {@code fallback} if no such value exists. - */ - public String optString(int index, String fallback) { - return optString(index, fallback, false); - } - - public String optString(int index, String fallback, boolean strict) { - try { - return getString(index, strict); - } catch (JsonException e) { - return fallback; - } - } - - - /** - * Returns the value at {@code index} if it exists and is a {@code - * JsonArray}. - * - * @throws JsonException if the value doesn't exist or is not a {@code - * JsonArray}. - */ - public JsonArray getJsonArray(int index) throws JsonException { - JsonElement el = get(index); - if (!el.isJsonArray()) { - throw Util.typeMismatch(index, el, "JsonArray"); - } - return el.asJsonArray(); - } - - /** - * Returns the value at {@code index} if it exists and is a {@code - * JsonArray}. Returns null otherwise. - */ - public JsonArray optJsonArray(int index) { - JsonElement el; - try { - el = get(index); - } catch (JsonException e) { - return null; - } - if (!el.isJsonArray()) { - return null; - } - return el.asJsonArray(); - } - - /** - * Returns the value at {@code index} if it exists and is a {@code - * JsonObject}. - * - * @throws JsonException if the value doesn't exist or is not a {@code - * JsonObject}. - */ - public JsonObject getJsonObject(int index) throws JsonException { - JsonElement el = get(index); - if (!el.isJsonObject()) { - throw Util.typeMismatch(index, el, "JsonObject"); - } - return el.asJsonObject(); - } - - /** - * Returns the value at {@code index} if it exists and is a {@code - * JsonObject}. Returns null otherwise. - */ - public JsonObject optJsonObject(int index) { - JsonElement el; - try { - el = get(index); - } catch (JsonException e) { - return null; - } - if (!el.isJsonObject()) { - return null; - } - return el.asJsonObject(); - } - - /** - * Returns a new object whose values are the values in this array, and whose - * names are the values in {@code names}. Names and values are paired up by - * index from 0 through to the shorter array's length. Names that are not - * strings will be coerced to strings. This method returns null if either - * array is empty. - */ - public JsonObject toJsonObject(JsonArray names) throws JsonException { - JsonObject result = new JsonObject(); - int length = Math.min(names.length(), values.size()); - if (length == 0) { - return null; - } - for (int i = 0; i < length; i++) { - String name = names.opt(i).toString(); - if (opt(i) != null) - result.put(name, opt(i)); - } - return result; - } - - public ArrayList toArrayList() { - ArrayList res = new ArrayList(); - for (JsonElement el : values) { - res.add(el.toString()); - } - return res; - } - - public boolean isOnlyStrings() { - for (JsonElement el : values) { - if (!el.isString()) return false; - } - return true; - } - - public boolean isOnlyNumbers() { - for (JsonElement el : values) { - if (!el.isNumber()) return false; - } - return true; - } - - public boolean isOnlyBooleans() { - for (JsonElement el : values) { - if (!el.isBoolean()) return false; - } - return true; - } - - public boolean isOnlyObjects() { - for (JsonElement el : values) { - if (!el.isJsonObject()) return false; - } - return true; - } - - public boolean isOnlyArrays() { - for (JsonElement el : values) { - if (!el.isJsonArray()) return false; - } - return true; - } - - @Override - public void write(JsonWriter writer) throws IOException { - writer.beginArray(); - for (JsonElement e : this) { - e.write(writer); - } - writer.endArray(); - } - - @Override - public boolean isJsonArray() { - return true; - } - - @Override - public JsonArray asJsonArray() { - return this; - } - - @Override - public boolean equals(Object o) { - return o instanceof JsonArray && ((JsonArray) o).values.equals(values); - } - - @Override - public int hashCode() { - // diverge from the original, which doesn't implement hashCode - return values.hashCode(); - } - - @Override - public int size() { - return values.size(); - } - - @Override - public boolean isEmpty() { - return values.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return values.contains(o); - } - - @Override - public Iterator iterator() { - return values.iterator(); - } - - @Override - public Object[] toArray() { - return values.toArray(); - } - - @Override - public T[] toArray(T[] ts) { - return values.toArray(ts); - } - - @Override - public boolean add(JsonElement jsonElement) { - checkIfFrozen(); - return values.add(jsonElement); - } - - @Override - public boolean remove(Object o) { - checkIfFrozen(); - return values.remove(o); - } - - @Override - public boolean containsAll(Collection objects) { - return values.containsAll(objects); - } - - @Override - public boolean addAll(Collection jsonElements) { - checkIfFrozen(); - return values.addAll(jsonElements); - } - - @Override - public boolean addAll(int i, Collection jsonElements) { - checkIfFrozen(); - return addAll(jsonElements); - } - - @Override - public boolean removeAll(Collection objects) { - checkIfFrozen(); - return values.removeAll(objects); - } - - @Override - public boolean retainAll(Collection objects) { - checkIfFrozen(); - return values.retainAll(objects); - } - - @Override - public void clear() { - checkIfFrozen(); - values.clear(); - } - - @Override - public String getJsonType() { - return TYPE_ARRAY; - } - - @Override - public boolean isFrozen() { - return frozen; - } - - @Override - public JsonArray freeze() { - frozen = true; - for (JsonElement el : values) { - if (el.isJsonArray()) { - el.asJsonArray().freeze(); - } - if (el.isJsonObject()) { - el.asJsonObject().freeze(); - } - } - return this; - } - - @Override - public JsonArray cloneAsThawed() { - try { - return JsonElement.readFrom(this.toString()).asJsonArray(); - } catch (IOException e) { - e.printStackTrace(); - throw new JsonException("Cannot Recreate Json Array", e); - } - } - - public void checkIfFrozen() { - if (isFrozen()) { - throw new IllegalStateException( - "Attempt to modify a frozen JsonArray instance."); - } - } - - void putInternal(JsonElement value) { - if (value != null) { - values.add(value); - } - } +public final class JsonArray extends JsonElement implements List, + Freezable { + + private volatile boolean frozen = false; + private final List values; + + /** + * Creates a {@code JsonArray} with no values. + */ + public JsonArray() { + values = new ArrayList(); + } + + /** + * Creates a new {@code JsonArray} by copying all values from the given + * collection. + * + * @param copyFrom + * a collection whose values are of supported types. Unsupported + * values are not permitted and will yield an array in an + * inconsistent state. + */ + /* Accept a raw type for API compatibility */ + public JsonArray(final Collection copyFrom) throws JsonException { + this(); + if (copyFrom != null) { + for (Object aCopyFrom : copyFrom) { + put(JsonElement.wrap(aCopyFrom)); + } + } + } + + /** + * Creates a new {@code JsonArray} with values from the given primitive + * array. + */ + public JsonArray(final Object array) throws JsonException { + if (!array.getClass().isArray()) { + throw new JsonException("Not a primitive array: " + + array.getClass()); + } + final int length = Array.getLength(array); + values = new ArrayList(length); + for (int i = 0; i < length; ++i) { + put(JsonElement.wrap(Array.get(array, i))); + } + } + + public void add(final int i, final JsonElement jsonElement) { + checkIfFrozen(); + put(i, jsonElement); + } + + public boolean add(final JsonElement jsonElement) { + checkIfFrozen(); + return values.add(jsonElement); + } + + public boolean addAll(final Collection jsonElements) { + checkIfFrozen(); + return values.addAll(jsonElements); + } + + public boolean addAll(final int i, + final Collection jsonElements) { + checkIfFrozen(); + return addAll(jsonElements); + } + + @Override + public JsonArray asJsonArray() { + return this; + } + + public void checkIfFrozen() { + if (isFrozen()) { + throw new IllegalStateException( + "Attempt to modify a frozen JsonArray instance."); + } + } + + public void clear() { + checkIfFrozen(); + values.clear(); + } + + public JsonArray cloneAsThawed() { + try { + return JsonElement.readFrom(this.toString()).asJsonArray(); + } catch (IOException e) { + e.printStackTrace(); + throw new JsonException("Cannot Recreate Json Array", e); + } + } + + public boolean contains(final Object o) { + return values.contains(o); + } + + public boolean containsAll(final Collection objects) { + return values.containsAll(objects); + } + + @Override + public boolean equals(final Object o) { + return o instanceof JsonArray && ((JsonArray) o).values.equals(values); + } + + public JsonArray freeze() { + frozen = true; + for (JsonElement el : values) { + if (el.isJsonArray()) { + el.asJsonArray().freeze(); + } + if (el.isJsonObject()) { + el.asJsonObject().freeze(); + } + } + return this; + } + + /** + * Returns the value at {@code index}. + * + * @throws JsonException + * if this array has no value at {@code index}, or if that value + * is the {@code null} reference. This method returns normally + * if the value is {@code JsonObject#NULL}. + */ + public JsonElement get(final int index) throws JsonException { + try { + JsonElement value = values.get(index); + if (value == null) { + throw new JsonException("Value at " + index + " is null."); + } + return value; + } catch (IndexOutOfBoundsException e) { + throw new JsonException("Index " + index + " out of range [0.." + + values.size() + ")"); + } + } + + public Boolean getBoolean(final int index) throws JsonException { + return getBoolean(index, false); + } + + /** + * Returns the value at {@code index} if it exists and is a boolean or can + * be coerced to a boolean. + * + * @throws JsonException + * if the value at {@code index} doesn't exist or cannot be + * coerced to a boolean. + */ + public Boolean getBoolean(final int index, final boolean strict) + throws JsonException { + JsonElement el = get(index); + Boolean res = null; + if (strict && !el.isBoolean()) { + throw Util.typeMismatch(index, el, "boolean", true); + } + if (el.isBoolean()) { + res = el.asBoolean(); + } + if (el.isString()) { + res = Util.toBoolean(el.asString()); + } + if (res == null) { + throw Util.typeMismatch(index, el, "boolean", strict); + } + return res; + } + + public Double getDouble(final int index) throws JsonException { + return getDouble(index, false); + } + + /** + * Returns the value at {@code index} if it exists and is a double or can be + * coerced to a double. + * + * @throws JsonException + * if the value at {@code index} doesn't exist or cannot be + * coerced to a double. + */ + public Double getDouble(final int index, final boolean strict) + throws JsonException { + JsonElement el = get(index); + Double res = null; + if (strict && !el.isNumber()) { + throw Util.typeMismatch(index, el, "double", true); + } + if (el.isNumber()) { + res = el.asDouble(); + } + if (el.isString()) { + res = Util.toDouble(el.asString()); + } + if (res == null) { + throw Util.typeMismatch(index, el, "double", strict); + } + return res; + } + + /** + * Returns the value at {@code index} if it exists and is an int or can be + * coerced to an int. + * + * @throws JsonException + * if the value at {@code index} doesn't exist or cannot be + * coerced to a int. + */ + public Integer getInt(final int index) throws JsonException { + return getInt(index, false); + } + + /** + * Returns the value at {@code index} if it exists and is an int or can be + * coerced to an int. Returns 0 otherwise. + */ + public Integer getInt(final int index, final boolean strict) + throws JsonException { + JsonElement el = get(index); + Integer res = null; + if (strict && !el.isNumber()) { + throw Util.typeMismatch(index, el, "int", true); + } + if (el.isNumber()) { + res = el.asInt(); + } + if (el.isString()) { + res = Util.toInteger(el.asString()); + } + if (res == null) { + throw Util.typeMismatch(index, el, "int", strict); + } + return res; + } + + /** + * Returns the value at {@code index} if it exists and is a + * {@code JsonArray}. + * + * @throws JsonException + * if the value doesn't exist or is not a {@code JsonArray}. + */ + public JsonArray getJsonArray(final int index) throws JsonException { + JsonElement el = get(index); + if (!el.isJsonArray()) { + throw Util.typeMismatch(index, el, "JsonArray"); + } + return el.asJsonArray(); + } + + /** + * Returns the value at {@code index} if it exists and is a + * {@code JsonObject}. + * + * @throws JsonException + * if the value doesn't exist or is not a {@code JsonObject}. + */ + public JsonObject getJsonObject(final int index) throws JsonException { + JsonElement el = get(index); + if (!el.isJsonObject()) { + throw Util.typeMismatch(index, el, "JsonObject"); + } + return el.asJsonObject(); + } + + @Override + public String getJsonType() { + return TYPE_ARRAY; + } + + public Long getLong(final int index) throws JsonException { + return getLong(index, false); + } + + /** + * Returns the value at {@code index} if it exists and is a long or can be + * coerced to a long. + * + * @throws JsonException + * if the value at {@code index} doesn't exist or cannot be + * coerced to a long. + */ + + public Long getLong(final int index, final boolean strict) + throws JsonException { + JsonElement el = get(index); + Long res = null; + if (strict && !el.isNumber()) { + throw Util.typeMismatch(index, el, "long", true); + } + if (el.isNumber()) { + res = el.asLong(); + } + if (el.isString()) { + res = Util.toLong(el.asString()); + } + if (res == null) { + throw Util.typeMismatch(index, el, "long", strict); + } + return res; + } + + public String getString(final int index) throws JsonException { + return getString(index, false); + } + + /** + * Returns the value at {@code index} if it exists, coercing it if + * necessary. + * + * @throws JsonException + * if no such value exists. + */ + public String getString(final int index, final boolean strict) + throws JsonException { + JsonElement el = get(index); + String res = null; + if (strict && !el.isString()) { + throw Util.typeMismatch(index, el, "string", true); + } + res = el.toString(); + if (res == null) { + throw Util.typeMismatch(index, el, "string", strict); + } + return res; + } + + @Override + public int hashCode() { + // diverge from the original, which doesn't implement hashCode + return values.hashCode(); + } + + public int indexOf(final Object o) { + return values.indexOf(o); + } + + public boolean isEmpty() { + return values.isEmpty(); + } + + public boolean isFrozen() { + return frozen; + } + + @Override + public boolean isJsonArray() { + return true; + } + + /** + * Returns true if this array has no value at {@code index}, or if its value + * is the {@code null} reference . + */ + public boolean isNull(final int index) { + Object value = opt(index); + return value == null || value.equals(JSON_NULL); + } + + public boolean isOnlyArrays() { + for (JsonElement el : values) { + if (!el.isJsonArray()) { + return false; + } + } + return true; + } + + public boolean isOnlyBooleans() { + for (JsonElement el : values) { + if (!el.isBoolean()) { + return false; + } + } + return true; + } + + public boolean isOnlyNumbers() { + for (JsonElement el : values) { + if (!el.isNumber()) { + return false; + } + } + return true; + } + + public boolean isOnlyObjects() { + for (JsonElement el : values) { + if (!el.isJsonObject()) { + return false; + } + } + return true; + } + + public boolean isOnlyStrings() { + for (JsonElement el : values) { + if (!el.isString()) { + return false; + } + } + return true; + } + + public Iterator iterator() { + return values.iterator(); + } + + public int lastIndexOf(final Object o) { + return values.lastIndexOf(o); + } + + /** + * Returns the number of values in this array. + */ + public int length() { + return values.size(); + } + + public ListIterator listIterator() { + return values.listIterator(); + } + + public ListIterator listIterator(final int i) { + return values.listIterator(i); + } + + /** + * Returns the value at {@code index}, or null if the array has no value at + * {@code index}. + */ + public JsonElement opt(final int index) { + if (index < 0 || index >= values.size()) { + return null; + } + return values.get(index); + } + + /** + * Returns the value at {@code index} if it exists and is a boolean or can + * be coerced to a boolean. Returns false otherwise. + */ + public Boolean optBoolean(final int index) { + return optBoolean(index, null); + } + + /** + * Returns the value at {@code index} if it exists and is a boolean or can + * be coerced to a boolean. Returns {@code fallback} otherwise. + */ + public Boolean optBoolean(final int index, final Boolean fallback) { + return optBoolean(index, fallback, false); + } + + public Boolean optBoolean(final int index, final Boolean fallback, + final boolean strict) { + try { + return getBoolean(index, strict); + } catch (JsonException e) { + return fallback; + } + } + + /** + * Returns the value at {@code index} if it exists and is a double or can be + * coerced to a double. Returns {@code NaN} otherwise. + */ + public Double optDouble(final int index) { + return optDouble(index, null); + } + + /** + * Returns the value at {@code index} if it exists and is a double or can be + * coerced to a double. Returns {@code fallback} otherwise. + */ + public Double optDouble(final int index, final Double fallback) { + return optDouble(index, fallback, false); + } + + public Double optDouble(final int index, final Double fallback, + final boolean strict) { + try { + return getDouble(index, strict); + } catch (JsonException e) { + return fallback; + } + } + + public Integer optInt(final int index) { + return optInt(index, null); + } + + /** + * Returns the value at {@code index} if it exists and is an int or can be + * coerced to an int. Returns {@code fallback} otherwise. + */ + public Integer optInt(final int index, final Integer fallback) { + return optInt(index, fallback, false); + } + + public Integer optInt(final int index, final Integer fallback, + final boolean strict) { + try { + return getInt(index, strict); + } catch (JsonException e) { + return fallback; + } + } + + /** + * Returns the value at {@code index} if it exists and is a + * {@code JsonArray}. Returns null otherwise. + */ + public JsonArray optJsonArray(final int index) { + JsonElement el; + try { + el = get(index); + } catch (JsonException e) { + return null; + } + if (!el.isJsonArray()) { + return null; + } + return el.asJsonArray(); + } + + /** + * Returns the value at {@code index} if it exists and is a + * {@code JsonObject}. Returns null otherwise. + */ + public JsonObject optJsonObject(final int index) { + JsonElement el; + try { + el = get(index); + } catch (JsonException e) { + return null; + } + if (!el.isJsonObject()) { + return null; + } + return el.asJsonObject(); + } + + /** + * Returns the value at {@code index} if it exists and is a long or can be + * coerced to a long. Returns 0 otherwise. + */ + public Long optLong(final int index) { + return optLong(index, null); + } + + /** + * Returns the value at {@code index} if it exists and is a long or can be + * coerced to a long. Returns {@code fallback} otherwise. + */ + public Long optLong(final int index, final Long fallback) { + return optLong(index, fallback, false); + } + + public Long optLong(final int index, final Long fallback, + final boolean strict) { + try { + return getLong(index, strict); + } catch (JsonException e) { + return fallback; + } + } + + /** + * Returns the value at {@code index} if it exists, coercing it if + * necessary. Returns the empty string if no such value exists. + */ + public String optString(final int index) { + return optString(index, null); + } + + /** + * Returns the value at {@code index} if it exists, coercing it if + * necessary. Returns {@code fallback} if no such value exists. + */ + public String optString(final int index, final String fallback) { + return optString(index, fallback, false); + } + + public String optString(final int index, final String fallback, + final boolean strict) { + try { + return getString(index, strict); + } catch (JsonException e) { + return fallback; + } + } + + /** + * Appends {@code value} to the end of this array. + * + * @return this array. + */ + public JsonArray put(final boolean value) { + return put(new JsonBoolean(value)); + } + + /** + * Appends {@code value} to the end of this array. + * + * @param value + * a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this array. + */ + public JsonArray put(final double value) throws JsonException { + return put(new JsonNumber(value)); + } + + /** + * Appends {@code value} to the end of this array. + * + * @return this array. + */ + public JsonArray put(final int value) { + return put(new JsonNumber(value)); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array + * to the required length if necessary. If a value already exists at + * {@code index}, it will be replaced. + * + * @return this array. + */ + public JsonArray put(final int index, final boolean value) + throws JsonException { + return put(index, (Boolean) value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array + * to the required length if necessary. If a value already exists at + * {@code index}, it will be replaced. + * + * @param value + * a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this array. + */ + public JsonArray put(final int index, final double value) + throws JsonException { + return put(index, (Double) value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array + * to the required length if necessary. If a value already exists at + * {@code index}, it will be replaced. + * + * @return this array. + */ + public JsonArray put(final int index, final int value) throws JsonException { + return put(index, (Integer) value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array + * to the required length if necessary. If a value already exists at + * {@code index}, it will be replaced. + * + * @return this array. + */ + public JsonArray put(final int index, final long value) + throws JsonException { + return put(index, (Long) value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array + * to the required length if necessary. If a value already exists at + * {@code index}, it will be replaced. + * + * @param value + * a {@link JsonObject}, {@link JsonArray}, String, Boolean, + * Integer, Long, Double, or {@code null}. May not be + * {@link Double#isNaN() NaNs} or {@link Double#isInfinite() + * infinities}. + * @return this array. + */ + public JsonArray put(final int index, final Object value) + throws JsonException { + checkIfFrozen(); + while (values.size() <= index) { + values.add(JSON_NULL); + } + values.set(index, wrap(value)); + return this; + } + + public JsonArray put(final JsonElement value) { + checkIfFrozen(); + putInternal(value); + + return this; + } + + /** + * Appends {@code value} to the end of this array. + * + * @return this array. + */ + public JsonArray put(final long value) { + return put(new JsonNumber(value)); + } + + /** + * Appends {@code value} to the end of this array. + * + * @param value + * a {@link JsonObject}, {@link JsonArray}, String, Boolean, + * Integer, Long, Double, or {@code null}. May not be + * {@link Double#isNaN() NaNs} or {@link Double#isInfinite() + * infinities}. Unsupported values are not permitted and will + * cause the array to be in an inconsistent state. + * @return this array. + */ + public JsonArray put(final Object value) throws JsonException { + return put(wrap(value)); + } + + void putInternal(final JsonElement value) { + if (value != null) { + values.add(value); + } + } + + /** + * Removes and returns the value at {@code index}, or null if the array has + * no value at {@code index}. + */ + public JsonElement remove(final int index) { + checkIfFrozen(); + if (index < 0 || index >= values.size()) { + return null; + } + return values.remove(index); + } + + public boolean remove(final Object o) { + checkIfFrozen(); + return values.remove(o); + } + + public boolean removeAll(final Collection objects) { + checkIfFrozen(); + return values.removeAll(objects); + } + + public boolean retainAll(final Collection objects) { + checkIfFrozen(); + return values.retainAll(objects); + } + + public JsonElement set(final int i, final JsonElement jsonElement) { + checkIfFrozen(); + return values.set(i, jsonElement); + } + + public int size() { + return values.size(); + } + + public List subList(final int i, final int i2) { + return values.subList(i, i2); + } + + public Object[] toArray() { + return values.toArray(); + } + + public T[] toArray(final T[] ts) { + return values.toArray(ts); + } + + public ArrayList toArrayList() { + ArrayList res = new ArrayList(); + for (JsonElement el : values) { + res.add(el.toString()); + } + return res; + } + + /** + * Returns a new object whose values are the values in this array, and whose + * names are the values in {@code names}. Names and values are paired up by + * index from 0 through to the shorter array's length. Names that are not + * strings will be coerced to strings. This method returns null if either + * array is empty. + */ + public JsonObject toJsonObject(final JsonArray names) throws JsonException { + JsonObject result = new JsonObject(); + int length = Math.min(names.length(), values.size()); + if (length == 0) { + return null; + } + for (int i = 0; i < length; i++) { + String name = names.opt(i).toString(); + if (opt(i) != null) { + result.put(name, opt(i)); + } + } + return result; + } + + @Override + public void write(final JsonWriter writer) throws IOException { + writer.beginArray(); + for (JsonElement e : this) { + e.write(writer); + } + writer.endArray(); + } } diff --git a/json-core/src/main/java/io/apptik/json/JsonElement.java b/json-core/src/main/java/io/apptik/json/JsonElement.java index 778d244..a4661b8 100644 --- a/json-core/src/main/java/io/apptik/json/JsonElement.java +++ b/json-core/src/main/java/io/apptik/json/JsonElement.java @@ -1,5 +1,8 @@ package io.apptik.json; +import static io.apptik.json.JsonNull.JSON_NULL; +import io.apptik.json.exception.JsonException; + import java.io.IOException; import java.io.Reader; import java.io.StringReader; @@ -9,235 +12,249 @@ import java.util.Collection; import java.util.Map; -import io.apptik.json.exception.JsonException; - -import static io.apptik.json.JsonNull.JSON_NULL; - public abstract class JsonElement { - public static final String TYPE_OBJECT = "object"; - public static final String TYPE_ARRAY = "array"; - public static final String TYPE_STRING = "string"; - public static final String TYPE_NUMBER = "number"; - public static final String TYPE_INTEGER = "integer"; - public static final String TYPE_BOOLEAN = "boolean"; - public static final String TYPE_NULL = "null"; - - public static JsonElement readFrom(JsonReader reader) throws JsonException, IOException { - return Adapter.fromJson(reader); - } - - public static JsonElement readFrom(Reader reader) throws JsonException, IOException { - return Adapter.fromJson(reader); - } - - public static JsonElement readFrom(String text) throws JsonException, IOException { - return JsonElement.readFrom(new StringReader(text)); - - } - - public boolean isNull() { - return false; - } - - public boolean isBoolean() { - return false; - } - - public boolean isNumber() { - return false; - } - - public boolean isString() { - return false; - } - - public boolean isJsonObject() { - return false; - } - - public boolean isJsonArray() { - return false; - } - - public boolean asBoolean() { - throw new UnsupportedOperationException(toString() + " is not a boolean"); - } - - public double asDouble() { - throw new UnsupportedOperationException(toString() + " is not a double number"); - } - - public float asFloat() { - throw new UnsupportedOperationException(toString() + " is not a float number"); - } - - public int asInt() { - throw new UnsupportedOperationException(toString() + " is not an integer number"); - } - - public long asLong() { - throw new UnsupportedOperationException(toString() + " is not a long integer number"); - } - - public byte asByte() { - throw new UnsupportedOperationException(toString() + " is not a byte number"); - } - - public String asString() { - throw new UnsupportedOperationException(toString() + " is not a string"); - } - - public JsonObject asJsonObject() { - throw new UnsupportedOperationException(toString() + " is not a json object"); - } - - public JsonArray asJsonArray() { - throw new UnsupportedOperationException(toString() + " is not an json array"); - } - - public void writeTo(Writer writer) throws IOException { - write(new JsonWriter(writer)); - } - - @Override - public String toString() { - StringWriter stringWriter = new StringWriter(); - JsonWriter jsonWriter = new JsonWriter(stringWriter); - try { - write(jsonWriter); - } catch (IOException exception) { - // StringWriter does not throw IOExceptions - throw new RuntimeException(exception); - } - return stringWriter.toString(); - } - - @Override - public boolean equals(Object object) { - return super.equals(object); - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - /** - * Wraps the given object if to JsonXXX object. - */ - public static JsonElement wrap(Object o) throws JsonException { - if (o == null) { - //null value means not specified i.e.-> no valued will be mapped - //Json.null is specific value - return null; - } - if (o instanceof JsonElement) { - return (JsonElement) o; - } - if (o instanceof ElementWrapper) { - return ((ElementWrapper) o).getJson(); - } - if (o instanceof Collection) { - return new JsonArray((Collection) o); - } else if (o.getClass().isArray()) { - return new JsonArray(o); - } - if (o instanceof Map) { - return new JsonObject((Map) o); - } - if (o instanceof Boolean) { - return new JsonBoolean((Boolean) o); - } - if (o instanceof Number) { - return new JsonNumber((Number) o); - } - if (o instanceof String) { - return new JsonString((String) o); - } - if (o instanceof Character) { - return new JsonString(Character.toString((Character) o)); - } - if (o instanceof ByteBuffer) { - return new JsonString(((ByteBuffer) o).asCharBuffer().toString()); - } - - return new JsonString(o.toString()); - } - - public abstract void write(JsonWriter writer) throws IOException; - - public abstract String getJsonType(); - - private static class Adapter { - static public void write(JsonWriter out, JsonElement value) throws IOException { - //TODO should this actually happen?? - if (value == null) { - out.nullValue(); - } else { - value.write(out); - } - } - - static public void toJson(Writer out, JsonElement value) throws IOException { - JsonWriter writer = new JsonWriter(out); - write(writer, value); - } - - static public String toJson(JsonElement value) throws IOException { - StringWriter stringWriter = new StringWriter(); - toJson(stringWriter, value); - return stringWriter.toString(); - } - - static public JsonElement read(JsonReader in) throws IOException, JsonException { - switch (in.peek()) { - case STRING: - return new JsonString(in.nextString()); - case NUMBER: - return new JsonNumber(in.nextString()); - case BOOLEAN: - return new JsonBoolean(in.nextBoolean()); - case NULL: - in.nextNull(); - return JSON_NULL; - case BEGIN_ARRAY: - JsonArray array = new JsonArray(); - in.beginArray(); - while (in.hasNext()) { - array.putInternal(read(in)); - } - in.endArray(); - return array; - case BEGIN_OBJECT: - JsonObject object = new JsonObject(); - in.beginObject(); - while (in.hasNext()) { - object.putInternal(in.nextName(), read(in)); - } - in.endObject(); - return object; - case END_DOCUMENT: - case NAME: - case END_OBJECT: - case END_ARRAY: - default: - throw new IllegalArgumentException(); - } - } - - static public JsonElement fromJson(Reader in) throws IOException, JsonException { - JsonReader reader = new JsonReader(in); - return read(reader); - } - - static public JsonElement fromJson(JsonReader in) throws IOException, JsonException { - return read(in); - } - - static public JsonElement fromJson(String json) throws IOException, JsonException { - return fromJson(new StringReader(json)); - } - } + private static class Adapter { + static public JsonElement fromJson(final JsonReader in) + throws IOException, JsonException { + return read(in); + } + + static public JsonElement fromJson(final Reader in) throws IOException, + JsonException { + JsonReader reader = new JsonReader(in); + return read(reader); + } + + static public JsonElement fromJson(final String json) + throws IOException, JsonException { + return fromJson(new StringReader(json)); + } + + static public JsonElement read(final JsonReader in) throws IOException, + JsonException { + switch (in.peek()) { + case STRING: + return new JsonString(in.nextString()); + case NUMBER: + return new JsonNumber(in.nextString()); + case BOOLEAN: + return new JsonBoolean(in.nextBoolean()); + case NULL: + in.nextNull(); + return JSON_NULL; + case BEGIN_ARRAY: + JsonArray array = new JsonArray(); + in.beginArray(); + while (in.hasNext()) { + array.putInternal(read(in)); + } + in.endArray(); + return array; + case BEGIN_OBJECT: + JsonObject object = new JsonObject(); + in.beginObject(); + while (in.hasNext()) { + object.putInternal(in.nextName(), read(in)); + } + in.endObject(); + return object; + case END_DOCUMENT: + case NAME: + case END_OBJECT: + case END_ARRAY: + default: + throw new IllegalArgumentException(); + } + } + + static public String toJson(final JsonElement value) throws IOException { + StringWriter stringWriter = new StringWriter(); + toJson(stringWriter, value); + return stringWriter.toString(); + } + + static public void toJson(final Writer out, final JsonElement value) + throws IOException { + JsonWriter writer = new JsonWriter(out); + write(writer, value); + } + + static public void write(final JsonWriter out, final JsonElement value) + throws IOException { + // TODO should this actually happen?? + if (value == null) { + out.nullValue(); + } else { + value.write(out); + } + } + } + + public static final String TYPE_ARRAY = "array"; + public static final String TYPE_BOOLEAN = "boolean"; + public static final String TYPE_INTEGER = "integer"; + public static final String TYPE_NULL = "null"; + public static final String TYPE_NUMBER = "number"; + public static final String TYPE_OBJECT = "object"; + + public static final String TYPE_STRING = "string"; + + public static JsonElement readFrom(final JsonReader reader) + throws JsonException, IOException { + return Adapter.fromJson(reader); + } + + public static JsonElement readFrom(final Reader reader) + throws JsonException, IOException { + return Adapter.fromJson(reader); + } + + public static JsonElement readFrom(final String text) throws JsonException, + IOException { + return JsonElement.readFrom(new StringReader(text)); + + } + + /** + * Wraps the given object if to JsonXXX object. + */ + public static JsonElement wrap(final Object o) throws JsonException { + if (o == null) { + // null value means not specified i.e.-> no valued will be mapped + // Json.null is specific value + return null; + } + if (o instanceof JsonElement) { + return (JsonElement) o; + } + if (o instanceof ElementWrapper) { + return ((ElementWrapper) o).getJson(); + } + if (o instanceof Collection) { + return new JsonArray((Collection) o); + } else if (o.getClass().isArray()) { + return new JsonArray(o); + } + if (o instanceof Map) { + return new JsonObject((Map) o); + } + if (o instanceof Boolean) { + return new JsonBoolean((Boolean) o); + } + if (o instanceof Number) { + return new JsonNumber((Number) o); + } + if (o instanceof String) { + return new JsonString((String) o); + } + if (o instanceof Character) { + return new JsonString(Character.toString((Character) o)); + } + if (o instanceof ByteBuffer) { + return new JsonString(((ByteBuffer) o).asCharBuffer().toString()); + } + + return new JsonString(o.toString()); + } + + public boolean asBoolean() { + throw new UnsupportedOperationException(toString() + + " is not a boolean"); + } + + public byte asByte() { + throw new UnsupportedOperationException(toString() + + " is not a byte number"); + } + + public double asDouble() { + throw new UnsupportedOperationException(toString() + + " is not a double number"); + } + + public float asFloat() { + throw new UnsupportedOperationException(toString() + + " is not a float number"); + } + + public int asInt() { + throw new UnsupportedOperationException(toString() + + " is not an integer number"); + } + + public JsonArray asJsonArray() { + throw new UnsupportedOperationException(toString() + + " is not an json array"); + } + + public JsonObject asJsonObject() { + throw new UnsupportedOperationException(toString() + + " is not a json object"); + } + + public long asLong() { + throw new UnsupportedOperationException(toString() + + " is not a long integer number"); + } + + public String asString() { + throw new UnsupportedOperationException(toString() + " is not a string"); + } + + @Override + public boolean equals(final Object object) { + return super.equals(object); + } + + public abstract String getJsonType(); + + @Override + public int hashCode() { + return super.hashCode(); + } + + public boolean isBoolean() { + return false; + } + + public boolean isJsonArray() { + return false; + } + + public boolean isJsonObject() { + return false; + } + + public boolean isNull() { + return false; + } + + public boolean isNumber() { + return false; + } + + public boolean isString() { + return false; + } + + @Override + public String toString() { + StringWriter stringWriter = new StringWriter(); + JsonWriter jsonWriter = new JsonWriter(stringWriter); + try { + write(jsonWriter); + } catch (IOException exception) { + // StringWriter does not throw IOExceptions + throw new RuntimeException(exception); + } + return stringWriter.toString(); + } + + public abstract void write(JsonWriter writer) throws IOException; + + public void writeTo(final Writer writer) throws IOException { + write(new JsonWriter(writer)); + } } diff --git a/json-core/src/main/java/io/apptik/json/JsonObject.java b/json-core/src/main/java/io/apptik/json/JsonObject.java index 73b99ea..0e20404 100644 --- a/json-core/src/main/java/io/apptik/json/JsonObject.java +++ b/json-core/src/main/java/io/apptik/json/JsonObject.java @@ -17,6 +17,11 @@ package io.apptik.json; +import static io.apptik.json.JsonNull.JSON_NULL; +import io.apptik.json.exception.JsonException; +import io.apptik.json.util.Freezable; +import io.apptik.json.util.LinkedTreeMap; +import io.apptik.json.util.Util; import java.io.IOException; import java.util.ArrayList; @@ -26,823 +31,858 @@ import java.util.Map; import java.util.Set; -import io.apptik.json.exception.JsonException; -import io.apptik.json.util.Freezable; -import io.apptik.json.util.LinkedTreeMap; -import io.apptik.json.util.Util; - -import static io.apptik.json.JsonNull.JSON_NULL; - // Note: this class was written without inspecting the non-free org.json sourcecode. /** * A modifiable set of name/value mappings. Names are unique, non-null strings. * Values may be any mix of {@link JsonObject JsonObjects}, {@link JsonArray * JsonArrays}, Strings, Booleans, Integers, Longs, Doubles or {@link JsonNull}. - * Values may not be {@code null}, {@link Double#isNaN() NaNs}, {@link - * Double#isInfinite() infinities}, or of any type not listed here. + * Values may not be {@code null}, {@link Double#isNaN() NaNs}, + * {@link Double#isInfinite() infinities}, or of any type not listed here. + *

*

- *

This class can coerce values to another type when requested. + * This class can coerce values to another type when requested. *

*

- *

This class can look up both mandatory and optional values: + *

+ * This class can look up both mandatory and optional values: *

*

- *

Warning: this class represents null in two incompatible - * ways: the standard Java {@code null} reference, and the sentinel value {@link - * JsonNull}. In particular, calling {@code put(name, null)} removes the + *

+ * Warning: this class represents null in two incompatible + * ways: the standard Java {@code null} reference, and the sentinel value + * {@link JsonNull}. In particular, calling {@code put(name, null)} removes the * named entry from the object but {@code put(name, JsonObject.NULL)} stores an * entry whose value is {@code JsonObject.NULL}. *

- *

Instances of this class are not thread safe. This class is - * not designed for inheritance and should not be subclassed. - * In particular, self-use by overrideable methods is not specified. See - * Effective Java Item 17, "Design and Document or inheritance or else - * prohibit it" for further information. + *

+ * Instances of this class are not thread safe. This class is not designed for + * inheritance and should not be subclassed. In particular, self-use by + * overrideable methods is not specified. See Effective Java Item 17, + * "Design and Document or inheritance or else prohibit it" for further + * information. */ -public final class JsonObject extends JsonElement implements Iterable>, Freezable { - - private volatile boolean frozen = false; - private final LinkedTreeMap nameValuePairs = new LinkedTreeMap(); - - /** - * Creates a {@code JsonObject} with no name/value mappings. - */ - public JsonObject() { - - } - - - /** - * Creates a new {@code JSONObject} by copying all name/value mappings from - * the given map. - * - * @param copyFrom a map whose keys are of type {@link String} and whose - * values are of supported types. - * @throws NullPointerException if any of the map's keys are null. - */ - /* (accept a raw type for API compatibility) */ - public JsonObject(Map copyFrom) throws JsonException { - this(); - Map contentsTyped = (Map) copyFrom; - for (Map.Entry entry : contentsTyped.entrySet()) { - /* - * Deviate from the original by checking that keys are non-null and - * of the proper type. (We still defer validating the values). - */ - String key = (String) entry.getKey(); - if (key == null) { - throw new NullPointerException("key == null"); - } - nameValuePairs.put(key, wrap(entry.getValue())); - } - } - - - /** - * Returns the number of name/value mappings in this object. - */ - public int length() { - return nameValuePairs.size(); - } - - - /** - * Maps {@code name} to {@code value}, clobbering any existing name/value - * mapping with the same name. - * - * @return this object. - */ - public JsonObject put(String name, boolean value) throws JsonException { - return put(name, new JsonBoolean(value)); - } - - /** - * Maps {@code name} to {@code value}, clobbering any existing name/value - * mapping with the same name. - * - * @param value a finite value. May not be {@link Double#isNaN() NaNs} or - * {@link Double#isInfinite() infinities}. - * @return this object. - */ - public JsonObject put(String name, double value) throws JsonException { - return put(name, new JsonNumber(value)); - } - - /** - * Maps {@code name} to {@code value}, clobbering any existing name/value - * mapping with the same name. - * - * @return this object. - */ - public JsonObject put(String name, int value) throws JsonException { - return put(name, new JsonNumber(value)); - } - - /** - * Maps {@code name} to {@code value}, clobbering any existing name/value - * mapping with the same name. - * - * @return this object. - */ - public JsonObject put(String name, long value) throws JsonException { - return put(name, new JsonNumber(value)); - } - - /** - * Maps {@code name} to {@code value}, clobbering any existing name/value - * mapping with the same name. If the value is {@code null}, any existing - * mapping for {@code name} is removed. - * - * @param value a {@link JsonObject}, {@link JsonArray}, String, Boolean, - * Integer, Long, Double, {@code null}. May not be - * {@link Double#isNaN() NaNs} or {@link Double#isInfinite() - * infinities}. - * @return this object. - */ - public JsonObject put(String name, Object value) throws JsonException { - return put(name, wrap(value)); - } - - /** - * Maps {@code name} to {@code value}, clobbering any existing name/value - * mapping with the same name. If the value is {@code null}, any existing - * mapping for {@code name} is removed. - * - * @param value a {@link JsonObject}, {@link JsonArray}, {@link JsonString}, {@link JsonBoolean}, - * {@link JsonNumber}, {@link JsonNull}. May not be - * {@link Double#isNaN() NaNs} or {@link Double#isInfinite() - * infinities}. - * @return this object. - */ - public JsonObject put(String name, JsonElement value) throws JsonException { - checkIfFrozen(); - putInternal(name, value); - return this; - } - - - /** - * Equivalent to {@code put(name, value)} when both parameters are non-null; - * does nothing otherwise. - */ - public JsonObject putOpt(String name, Object value) throws JsonException { - if (name == null || value == null) { - return this; - } - return put(name, value); - } - - /** - * Appends {@code value} to the array already mapped to {@code name}. If - * this object has no mapping for {@code name}, this inserts a new mapping. - * If the mapping exists but its value is not an array, the existing - * and new values are inserted in order into a new array which is itself - * mapped to {@code name}. In aggregate, this allows values to be added to a - * mapping one at a time. - *

- *

Note that {@code append(String, Object)} provides better semantics. - * In particular, the mapping for {@code name} will always be a - * {@link JsonArray}. Using {@code accumulate} will result in either a - * {@link JsonArray} or a mapping whose type is the type of {@code value} - * depending on the number of calls to it. - * - * @param value a {@link JsonObject}, {@link JsonArray}, String, Boolean, - * Integer, Long, Double or null. May not be {@link - * Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}. - */ - // TODO: Change {@code append) to {@link #append} when append is - // unhidden. - public JsonObject accumulate(String name, Object value) throws JsonException { - checkIfFrozen(); - Object current = nameValuePairs.get(checkName(name)); - if (current == null) { - return put(name, value); - } - - if (current instanceof JsonArray) { - JsonArray array = (JsonArray) current; - array.put(value); - } else { - JsonArray array = new JsonArray(); - array.put(current); - array.put(value); - nameValuePairs.put(name, array); - } - return this; - } - - /** - * Appends values to the array mapped to {@code name}. A new {@link JsonArray} - * mapping for {@code name} will be inserted if no mapping exists. If the existing - * mapping for {@code name} is not a {@link JsonArray}, a {@link JsonException} - * will be thrown. - * - * @throws JsonException if {@code name} is {@code null} or if the mapping for - * {@code name} is non-null and is not a {@link JsonArray}. - * @hide - */ - public JsonObject append(String name, Object value) throws JsonException { - checkIfFrozen(); - Object current = nameValuePairs.get(checkName(name)); - - final JsonArray array; - if (current instanceof JsonArray) { - array = (JsonArray) current; - } else if (current == null) { - JsonArray newArray = new JsonArray(); - nameValuePairs.put(name, newArray); - array = newArray; - } else { - throw new JsonException("Key " + name + " is not a JsonArray"); - } - - array.put(value); - - return this; - } - - String checkName(String name) { - if (name == null) { - throw new JsonException("Names must be non-null"); - } - return name; - } - - /** - * Removes the named mapping if it exists; does nothing otherwise. - * - * @return the value previously mapped by {@code name}, or null if there was - * no such mapping. - */ - public Object remove(String name) { - checkIfFrozen(); - return nameValuePairs.remove(name); - } - - /** - * Returns true if this object has no mapping for {@code name} or if it has - * a mapping whose value is {@link JsonNull}. - */ - public boolean isNull(String name) { - JsonElement value = nameValuePairs.get(name); - return value.isNull(); - } - - /** - * Returns true if this object has a mapping for {@code name}. The mapping - * may be {@link JsonNull}. - */ - public boolean has(String name) { - return nameValuePairs.containsKey(name); - } - - /** - * Returns the value mapped by {@code name}, or throws if no such mapping exists. - * - * @throws JsonException if no such mapping exists. - */ - public JsonElement get(String name) throws JsonException { - JsonElement result = nameValuePairs.get(name); - if (result == null) { - throw new JsonException("No value for " + name + ", in: " + this.toString()); - } - return result; - } - - /** - * Returns the value mapped by {@code name}, or null if no such mapping - * exists. - */ - public JsonElement opt(String name) { - return nameValuePairs.get(name); - } - - /** - * Returns the value mapped by {@code name} if it exists and is a boolean or - * can be coerced to a boolean, or throws otherwise. - * - * @throws JsonException if the mapping doesn't exist or cannot be coerced - * to a boolean. - */ - public Boolean getBoolean(String name, boolean strict) throws JsonException { - JsonElement el = get(name); - Boolean res = null; - if (strict && !el.isBoolean()) { - throw Util.typeMismatch(name, el, "boolean", true); - } - if (el.isBoolean()) { - res = el.asBoolean(); - } - if (el.isString()) { - res = Util.toBoolean(el.asString()); - } - if (res == null) - throw Util.typeMismatch(name, el, "boolean", strict); - return res; - } - - /** - * Returns the value mapped by {@code name} if it exists and is a boolean or - * can be coerced to a boolean, or throws otherwise. - * - * @throws JsonException if the mapping doesn't exist or cannot be coerced - * to a boolean. - */ - public Boolean getBoolean(String name) throws JsonException { - return getBoolean(name, true); - } - - /** - * Returns the value mapped by {@code name} if it exists and is a boolean or - * can be coerced to a boolean, or false otherwise. - */ - public Boolean optBoolean(String name) { - return optBoolean(name, null); - } - - /** - * Returns the value mapped by {@code name} if it exists and is a boolean - * or {@code fallback} otherwise. - */ - public Boolean optBoolean(String name, Boolean fallback) { - return optBoolean(name, fallback, true); - } - - public Boolean optBoolean(String name, Boolean fallback, boolean strict) { - try { - return getBoolean(name, strict); - } catch (JsonException e) { - return fallback; - } - } - - /** - * Returns the value mapped by {@code name} if it exists and is a double or - * can be coerced to a double, or throws otherwise. - * - * @throws JsonException if the mapping doesn't exist or cannot be coerced - * to a double. - */ - public Double getDouble(String name, boolean strict) throws JsonException { - JsonElement el = get(name); - Double res = null; - if (strict && !el.isNumber()) { - throw Util.typeMismatch(name, el, "double", true); - } - if (el.isNumber()) { - res = el.asDouble(); - } - if (el.isString()) { - res = Util.toDouble(el.asString()); - } - if (res == null) - throw Util.typeMismatch(name, el, "double", strict); - return res; - } - - public Double getDouble(String name) throws JsonException { - return getDouble(name, true); - } - - /** - * Returns the value mapped by {@code name} if it exists and is a double or - * can be coerced to a double, or {@code NaN} otherwise. - */ - public Double optDouble(String name) { - return optDouble(name, null); - } - - /** - * Returns the value mapped by {@code name} if it exists and is a double or - * can be coerced to a double, or {@code fallback} otherwise. - */ - public Double optDouble(String name, Double fallback) { - return optDouble(name, fallback, true); - } - - public Double optDouble(String name, Double fallback, boolean strict) { - try { - return getDouble(name, strict); - } catch (JsonException e) { - return fallback; - } - } - - /** - * Returns the value mapped by {@code name} if it exists and is an int or - * can be coerced to an int, or throws otherwise. - * - * @throws JsonException if the mapping doesn't exist or cannot be coerced - * to an int. - */ - public Integer getInt(String name, boolean strict) throws JsonException { - JsonElement el = get(name); - Integer res = null; - if (strict && !el.isNumber()) { - throw Util.typeMismatch(name, el, "int", true); - } - if (el.isNumber()) { - res = el.asInt(); - } - if (el.isString()) { - res = Util.toInteger(el.asString()); - } - if (res == null) - throw Util.typeMismatch(name, el, "int", strict); - return res; - } - - public Integer getInt(String name) throws JsonException { - return getInt(name, true); - } - - /** - * Returns the value mapped by {@code name} if it exists and is an int or - * can be coerced to an int, or 0 otherwise. - */ - public Integer optInt(String name) { - return optInt(name, null); - } - - /** - * Returns the value mapped by {@code name} if it exists and is an int or - * can be coerced to an int, or {@code fallback} otherwise. - */ - public Integer optInt(String name, Integer fallback) { - return optInt(name, fallback, true); - } - - public Integer optInt(String name, Integer fallback, boolean strict) { - try { - return getInt(name, strict); - } catch (JsonException e) { - return fallback; - } - } - - /** - * Returns the value mapped by {@code name} if it exists and is a long or - * can be coerced to a long, or throws otherwise. - * Note that Util represents numbers as doubles, - * so this is lossy; use strings to transfer numbers via Util. - * - * @throws JsonException if the mapping doesn't exist or cannot be coerced - * to a long. - */ - public Long getLong(String name, boolean strict) throws JsonException { - JsonElement el = get(name); - Long res = null; - if (strict && !el.isNumber()) { - throw Util.typeMismatch(name, el, "long", true); - } - if (el.isNumber()) { - res = el.asLong(); - } - if (el.isString()) { - res = Util.toLong(el.asString()); - } - if (res == null) - throw Util.typeMismatch(name, el, "long", strict); - return res; - } - - public Long getLong(String name) throws JsonException { - return getLong(name, true); - } - - /** - * Returns the value mapped by {@code name} if it exists and is a long or - * can be coerced to a long, or 0 otherwise. Note that Util represents numbers as doubles, - * so this is lossy; use strings to transfer numbers via Util. - */ - public Long optLong(String name) { - return optLong(name, null); - } - - /** - * Returns the value mapped by {@code name} if it exists and is a long or - * can be coerced to a long, or {@code fallback} otherwise. Note that Util represents - * numbers as doubles, so this is lossy; use strings to transfer - * numbers via Util. - */ - public Long optLong(String name, Long fallback) { - return optLong(name, fallback, true); - } - - public Long optLong(String name, Long fallback, boolean strict) { - try { - return getLong(name, strict); - } catch (JsonException e) { - return fallback; - } - } - - /** - * Returns the value mapped by {@code name} if it exists, coercing it if - * necessary, or throws if no such mapping exists. - * - * @throws JsonException if no such mapping exists. - */ - public String getString(String name, boolean strict) throws JsonException { - JsonElement el = get(name); - String res = null; - if (strict && !el.isString()) { - throw Util.typeMismatch(name, el, "string", true); - } - res = el.toString(); - if (res == null) - throw Util.typeMismatch(name, el, "string", strict); - return res; - } - - public String getString(String name) throws JsonException { - return getString(name, true); - } - - /** - * Returns the value mapped by {@code name} if it exists, coercing it if - * necessary, or the empty string if no such mapping exists. - */ - public String optString(String name) { - return optString(name, null); - } - - /** - * Returns the value mapped by {@code name} if it exists, coercing it if - * necessary, or {@code fallback} if no such mapping exists. - */ - public String optString(String name, String fallback) { - return optString(name, fallback, true); - } - - public String optString(String name, String fallback, boolean strict) { - try { - return getString(name, strict); - } catch (JsonException e) { - return fallback; - } - } - - - /** - * Returns the value mapped by {@code name} if it exists and is a {@code - * JsonArray}, or throws otherwise. - * - * @throws JsonException if the mapping doesn't exist or is not a {@code - * JsonArray}. - */ - public JsonArray getJsonArray(String name) throws JsonException { - JsonElement el = get(name); - if (!el.isJsonArray()) { - throw Util.typeMismatch(name, el, "JsonArray"); - } - return el.asJsonArray(); - } - - /** - * Returns the value mapped by {@code name} if it exists and is a {@code - * JsonArray}, or null otherwise. - */ - public JsonArray optJsonArray(String name) { - JsonElement el = null; - try { - el = get(name); - } catch (JsonException e) { - return null; - } - if (!el.isJsonArray()) { - return null; - } - return el.asJsonArray(); - } - - /** - * Returns the value mapped by {@code name} if it exists and is a {@code - * JsonObject}, or throws otherwise. - * - * @throws JsonException if the mapping doesn't exist or is not a {@code - * JsonObject}. - */ - public JsonObject getJsonObject(String name) throws JsonException { - JsonElement el = get(name); - if (!el.isJsonObject()) { - throw Util.typeMismatch(name, el, "JsonObject"); - } - return el.asJsonObject(); - } - - /** - * Returns the value mapped by {@code name} if it exists and is a {@code - * JsonObject}, or null otherwise. - */ - public JsonObject optJsonObject(String name) { - JsonElement el = null; - try { - el = get(name); - } catch (JsonException e) { - return null; - } - if (!el.isJsonObject()) { - return null; - } - return el.asJsonObject(); - } - - /** - * Returns an array with the values corresponding to {@code names}. The - * array contains null for names that aren't mapped. This method returns - * null if {@code names} is either null or empty. - */ - public JsonArray toJsonArray(JsonArray names) throws JsonException { - JsonArray result = new JsonArray(); - if (names == null) { - return null; - } - int length = names.length(); - if (length == 0) { - return null; - } - for (int i = 0; i < length; i++) { - String name = Util.toString(names.opt(i)); - result.put(opt(name)); - } - return result; - } - - /** - * Returns an iterator of the {@code String} names in this object. The - * returned iterator supports {@link Iterator#remove() remove}, which will - * remove the corresponding mapping from this object. If this object is - * modified after the iterator is returned, the iterator's behavior is - * undefined. The order of the keys is undefined. - */ - public Iterator keys() { - return nameValuePairs.keySet().iterator(); - } - - /** - * Returns the set of {@code String} names in this object. The returned set - * is a view of the keys in this object. {@link Set#remove(Object)} will remove - * the corresponding mapping from this object and set iterator behaviour - * is undefined if this object is modified after it is returned. - *

- * See {@link #keys()}. - * - * @hide. - */ - public Set keySet() { - return nameValuePairs.keySet(); - } - - public Collection valuesSet() { - if (isFrozen()) Collections.unmodifiableCollection(nameValuePairs.values()); - return nameValuePairs.values(); - } - - /** - * Returns an array containing the string names in this object. This method - * returns null if this object contains no mappings. - */ - public JsonArray names() throws JsonException { - return nameValuePairs.isEmpty() - ? null - : new JsonArray(new ArrayList(nameValuePairs.keySet())); - } - - @Override - public void write(JsonWriter writer) throws IOException { - writer.beginObject(); - for (Map.Entry e : nameValuePairs.entrySet()) { - writer.name(e.getKey()); - e.getValue().write(writer); - } - writer.endObject(); - } - - @Override - public boolean isJsonObject() { - return true; - } - - @Override - public JsonObject asJsonObject() { - return this; - } - - @Override - public boolean equals(Object o) { - return o instanceof JsonObject && ((JsonObject) o).nameValuePairs.equals(nameValuePairs); - } - - @Override - public String getJsonType() { - return TYPE_OBJECT; - } - - @Override - public Iterator> iterator() { - return nameValuePairs.entrySet().iterator(); - } - - /** - * Merge Json Object with another Json Object. - * It does not change element of another with the same name exists. - * However if the element is Json Object then it will go down and merge that object. - * - * @param another - * @return - */ - public JsonObject merge(JsonObject another) { - for (Map.Entry anotherEntry : another) { - JsonElement curr = this.opt(anotherEntry.getKey()); - if (curr == null) { - try { - this.put(anotherEntry.getKey(), anotherEntry.getValue()); - } catch (JsonException e) { - e.printStackTrace(); - } - } else if (curr.isJsonObject() && anotherEntry.getValue().isJsonObject()) { - curr.asJsonObject().merge(anotherEntry.getValue().asJsonObject()); - } - } - return this; - } - - public JsonObject clear() { - checkIfFrozen(); - nameValuePairs.clear(); - return this; - } - - - @Override - public boolean isFrozen() { - return frozen; - } - - @Override - public JsonObject freeze() { - frozen = true; - for (JsonElement el : nameValuePairs.values()) { - if (el.isJsonArray()) { - el.asJsonArray().freeze(); - } - if (el.isJsonObject()) { - el.asJsonObject().freeze(); - } - } - return this; - } - - @Override - public JsonObject cloneAsThawed() { - try { - return JsonElement.readFrom(this.toString()).asJsonObject(); - } catch (IOException e) { - e.printStackTrace(); - throw new JsonException("Cannot Recreate Json Object", e); - } - } - - public void checkIfFrozen() { - if (isFrozen()) { - throw new IllegalStateException( - "Attempt to modify a frozen JsonObject instance."); - } - } - - void putInternal(String name, JsonElement value) { - if (value == null) { - value = JSON_NULL; - } - nameValuePairs.put(checkName(name), value); - } +public final class JsonObject extends JsonElement implements + Iterable>, Freezable { + + private volatile boolean frozen = false; + private final LinkedTreeMap nameValuePairs = new LinkedTreeMap(); + + /** + * Creates a {@code JsonObject} with no name/value mappings. + */ + public JsonObject() { + + } + + /** + * Creates a new {@code JSONObject} by copying all name/value mappings from + * the given map. + * + * @param copyFrom + * a map whose keys are of type {@link String} and whose values + * are of supported types. + * @throws NullPointerException + * if any of the map's keys are null. + */ + /* (accept a raw type for API compatibility) */ + public JsonObject(final Map copyFrom) throws JsonException { + this(); + Map contentsTyped = copyFrom; + for (Map.Entry entry : contentsTyped.entrySet()) { + /* + * Deviate from the original by checking that keys are non-null and + * of the proper type. (We still defer validating the values). + */ + String key = (String) entry.getKey(); + if (key == null) { + throw new NullPointerException("key == null"); + } + nameValuePairs.put(key, wrap(entry.getValue())); + } + } + + /** + * Appends {@code value} to the array already mapped to {@code name}. If + * this object has no mapping for {@code name}, this inserts a new mapping. + * If the mapping exists but its value is not an array, the existing and new + * values are inserted in order into a new array which is itself mapped to + * {@code name}. In aggregate, this allows values to be added to a mapping + * one at a time. + *

+ *

+ * Note that {@code append(String, Object)} provides better semantics. In + * particular, the mapping for {@code name} will always be a + * {@link JsonArray}. Using {@code accumulate} will result in either a + * {@link JsonArray} or a mapping whose type is the type of {@code value} + * depending on the number of calls to it. + * + * @param value + * a {@link JsonObject}, {@link JsonArray}, String, Boolean, + * Integer, Long, Double or null. May not be + * {@link Double#isNaN() NaNs} or {@link Double#isInfinite() + * infinities}. + */ + // TODO: Change {@code append) to {@link #append} when append is + // unhidden. + public JsonObject accumulate(final String name, final Object value) + throws JsonException { + checkIfFrozen(); + Object current = nameValuePairs.get(checkName(name)); + if (current == null) { + return put(name, value); + } + + if (current instanceof JsonArray) { + JsonArray array = (JsonArray) current; + array.put(value); + } else { + JsonArray array = new JsonArray(); + array.put(current); + array.put(value); + nameValuePairs.put(name, array); + } + return this; + } + + /** + * Appends values to the array mapped to {@code name}. A new + * {@link JsonArray} mapping for {@code name} will be inserted if no mapping + * exists. If the existing mapping for {@code name} is not a + * {@link JsonArray}, a {@link JsonException} will be thrown. + * + * @throws JsonException + * if {@code name} is {@code null} or if the mapping for + * {@code name} is non-null and is not a {@link JsonArray}. + * @hide + */ + public JsonObject append(final String name, final Object value) + throws JsonException { + checkIfFrozen(); + Object current = nameValuePairs.get(checkName(name)); + + final JsonArray array; + if (current instanceof JsonArray) { + array = (JsonArray) current; + } else if (current == null) { + JsonArray newArray = new JsonArray(); + nameValuePairs.put(name, newArray); + array = newArray; + } else { + throw new JsonException("Key " + name + " is not a JsonArray"); + } + + array.put(value); + + return this; + } + + @Override + public JsonObject asJsonObject() { + return this; + } + + public void checkIfFrozen() { + if (isFrozen()) { + throw new IllegalStateException( + "Attempt to modify a frozen JsonObject instance."); + } + } + + String checkName(final String name) { + if (name == null) { + throw new JsonException("Names must be non-null"); + } + return name; + } + + public JsonObject clear() { + checkIfFrozen(); + nameValuePairs.clear(); + return this; + } + + public JsonObject cloneAsThawed() { + try { + return JsonElement.readFrom(this.toString()).asJsonObject(); + } catch (IOException e) { + e.printStackTrace(); + throw new JsonException("Cannot Recreate Json Object", e); + } + } + + @Override + public boolean equals(final Object o) { + return o instanceof JsonObject + && ((JsonObject) o).nameValuePairs.equals(nameValuePairs); + } + + public JsonObject freeze() { + frozen = true; + for (JsonElement el : nameValuePairs.values()) { + if (el.isJsonArray()) { + el.asJsonArray().freeze(); + } + if (el.isJsonObject()) { + el.asJsonObject().freeze(); + } + } + return this; + } + + /** + * Returns the value mapped by {@code name}, or throws if no such mapping + * exists. + * + * @throws JsonException + * if no such mapping exists. + */ + public JsonElement get(final String name) throws JsonException { + JsonElement result = nameValuePairs.get(name); + if (result == null) { + throw new JsonException("No value for " + name + ", in: " + + this.toString()); + } + return result; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a boolean or + * can be coerced to a boolean, or throws otherwise. + * + * @throws JsonException + * if the mapping doesn't exist or cannot be coerced to a + * boolean. + */ + public Boolean getBoolean(final String name) throws JsonException { + return getBoolean(name, true); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a boolean or + * can be coerced to a boolean, or throws otherwise. + * + * @throws JsonException + * if the mapping doesn't exist or cannot be coerced to a + * boolean. + */ + public Boolean getBoolean(final String name, final boolean strict) + throws JsonException { + JsonElement el = get(name); + Boolean res = null; + if (strict && !el.isBoolean()) { + throw Util.typeMismatch(name, el, "boolean", true); + } + if (el.isBoolean()) { + res = el.asBoolean(); + } + if (el.isString()) { + res = Util.toBoolean(el.asString()); + } + if (res == null) { + throw Util.typeMismatch(name, el, "boolean", strict); + } + return res; + } + + public Double getDouble(final String name) throws JsonException { + return getDouble(name, true); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a double or + * can be coerced to a double, or throws otherwise. + * + * @throws JsonException + * if the mapping doesn't exist or cannot be coerced to a + * double. + */ + public Double getDouble(final String name, final boolean strict) + throws JsonException { + JsonElement el = get(name); + Double res = null; + if (strict && !el.isNumber()) { + throw Util.typeMismatch(name, el, "double", true); + } + if (el.isNumber()) { + res = el.asDouble(); + } + if (el.isString()) { + res = Util.toDouble(el.asString()); + } + if (res == null) { + throw Util.typeMismatch(name, el, "double", strict); + } + return res; + } + + public Integer getInt(final String name) throws JsonException { + return getInt(name, true); + } + + /** + * Returns the value mapped by {@code name} if it exists and is an int or + * can be coerced to an int, or throws otherwise. + * + * @throws JsonException + * if the mapping doesn't exist or cannot be coerced to an int. + */ + public Integer getInt(final String name, final boolean strict) + throws JsonException { + JsonElement el = get(name); + Integer res = null; + if (strict && !el.isNumber()) { + throw Util.typeMismatch(name, el, "int", true); + } + if (el.isNumber()) { + res = el.asInt(); + } + if (el.isString()) { + res = Util.toInteger(el.asString()); + } + if (res == null) { + throw Util.typeMismatch(name, el, "int", strict); + } + return res; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a + * {@code JsonArray}, or throws otherwise. + * + * @throws JsonException + * if the mapping doesn't exist or is not a {@code JsonArray}. + */ + public JsonArray getJsonArray(final String name) throws JsonException { + JsonElement el = get(name); + if (!el.isJsonArray()) { + throw Util.typeMismatch(name, el, "JsonArray"); + } + return el.asJsonArray(); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a + * {@code JsonObject}, or throws otherwise. + * + * @throws JsonException + * if the mapping doesn't exist or is not a {@code JsonObject}. + */ + public JsonObject getJsonObject(final String name) throws JsonException { + JsonElement el = get(name); + if (!el.isJsonObject()) { + throw Util.typeMismatch(name, el, "JsonObject"); + } + return el.asJsonObject(); + } + + @Override + public String getJsonType() { + return TYPE_OBJECT; + } + + public Long getLong(final String name) throws JsonException { + return getLong(name, true); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a long or + * can be coerced to a long, or throws otherwise. Note that Util represents + * numbers as doubles, so this is lossy; use strings to + * transfer numbers via Util. + * + * @throws JsonException + * if the mapping doesn't exist or cannot be coerced to a long. + */ + public Long getLong(final String name, final boolean strict) + throws JsonException { + JsonElement el = get(name); + Long res = null; + if (strict && !el.isNumber()) { + throw Util.typeMismatch(name, el, "long", true); + } + if (el.isNumber()) { + res = el.asLong(); + } + if (el.isString()) { + res = Util.toLong(el.asString()); + } + if (res == null) { + throw Util.typeMismatch(name, el, "long", strict); + } + return res; + } + + public String getString(final String name) throws JsonException { + return getString(name, true); + } + + /** + * Returns the value mapped by {@code name} if it exists, coercing it if + * necessary, or throws if no such mapping exists. + * + * @throws JsonException + * if no such mapping exists. + */ + public String getString(final String name, final boolean strict) + throws JsonException { + JsonElement el = get(name); + String res = null; + if (strict && !el.isString()) { + throw Util.typeMismatch(name, el, "string", true); + } + res = el.toString(); + if (res == null) { + throw Util.typeMismatch(name, el, "string", strict); + } + return res; + } + + /** + * Returns true if this object has a mapping for {@code name}. The mapping + * may be {@link JsonNull}. + */ + public boolean has(final String name) { + return nameValuePairs.containsKey(name); + } + + public boolean isFrozen() { + return frozen; + } + + @Override + public boolean isJsonObject() { + return true; + } + + /** + * Returns true if this object has no mapping for {@code name} or if it has + * a mapping whose value is {@link JsonNull}. + */ + public boolean isNull(final String name) { + JsonElement value = nameValuePairs.get(name); + return value.isNull(); + } + + public Iterator> iterator() { + return nameValuePairs.entrySet().iterator(); + } + + /** + * Returns an iterator of the {@code String} names in this object. The + * returned iterator supports {@link Iterator#remove() remove}, which will + * remove the corresponding mapping from this object. If this object is + * modified after the iterator is returned, the iterator's behavior is + * undefined. The order of the keys is undefined. + */ + public Iterator keys() { + return nameValuePairs.keySet().iterator(); + } + + /** + * Returns the set of {@code String} names in this object. The returned set + * is a view of the keys in this object. {@link Set#remove(Object)} will + * remove the corresponding mapping from this object and set iterator + * behaviour is undefined if this object is modified after it is returned. + *

+ * See {@link #keys()}. + * + * @hide. + */ + public Set keySet() { + return nameValuePairs.keySet(); + } + + /** + * Returns the number of name/value mappings in this object. + */ + public int length() { + return nameValuePairs.size(); + } + + /** + * Merge Json Object with another Json Object. It does not change element of + * another with the same name exists. However if the element is Json Object + * then it will go down and merge that object. + * + * @param another + * @return + */ + public JsonObject merge(final JsonObject another) { + for (Map.Entry anotherEntry : another) { + JsonElement curr = this.opt(anotherEntry.getKey()); + if (curr == null) { + try { + this.put(anotherEntry.getKey(), anotherEntry.getValue()); + } catch (JsonException e) { + e.printStackTrace(); + } + } else if (curr.isJsonObject() + && anotherEntry.getValue().isJsonObject()) { + curr.asJsonObject().merge( + anotherEntry.getValue().asJsonObject()); + } + } + return this; + } + + /** + * Returns an array containing the string names in this object. This method + * returns null if this object contains no mappings. + */ + public JsonArray names() throws JsonException { + return nameValuePairs.isEmpty() ? null : new JsonArray( + new ArrayList(nameValuePairs.keySet())); + } + + /** + * Returns the value mapped by {@code name}, or null if no such mapping + * exists. + */ + public JsonElement opt(final String name) { + return nameValuePairs.get(name); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a boolean or + * can be coerced to a boolean, or false otherwise. + */ + public Boolean optBoolean(final String name) { + return optBoolean(name, false); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a boolean or + * {@code fallback} otherwise. + */ + public Boolean optBoolean(final String name, final Boolean fallback) { + return optBoolean(name, fallback, true); + } + + public Boolean optBoolean(final String name, final Boolean fallback, + final boolean strict) { + try { + return getBoolean(name, strict); + } catch (JsonException e) { + return fallback; + } + } + + /** + * Returns the value mapped by {@code name} if it exists and is a double or + * can be coerced to a double, or {@code NaN} otherwise. + */ + public Double optDouble(final String name) { + return optDouble(name, null); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a double or + * can be coerced to a double, or {@code fallback} otherwise. + */ + public Double optDouble(final String name, final Double fallback) { + return optDouble(name, fallback, true); + } + + public Double optDouble(final String name, final Double fallback, + final boolean strict) { + try { + return getDouble(name, strict); + } catch (JsonException e) { + return fallback; + } + } + + /** + * Returns the value mapped by {@code name} if it exists and is an int or + * can be coerced to an int, or 0 otherwise. + */ + public Integer optInt(final String name) { + return optInt(name, 0); + } + + /** + * Returns the value mapped by {@code name} if it exists and is an int or + * can be coerced to an int, or {@code fallback} otherwise. + */ + public Integer optInt(final String name, final Integer fallback) { + return optInt(name, fallback, true); + } + + public Integer optInt(final String name, final Integer fallback, + final boolean strict) { + try { + return getInt(name, strict); + } catch (JsonException e) { + return fallback; + } + } + + /** + * Returns the value mapped by {@code name} if it exists and is a + * {@code JsonArray}, or null otherwise. + */ + public JsonArray optJsonArray(final String name) { + JsonElement el = null; + try { + el = get(name); + } catch (JsonException e) { + return null; + } + if (!el.isJsonArray()) { + return null; + } + return el.asJsonArray(); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a + * {@code JsonObject}, or null otherwise. + */ + public JsonObject optJsonObject(final String name) { + JsonElement el = null; + try { + el = get(name); + } catch (JsonException e) { + return null; + } + if (!el.isJsonObject()) { + return null; + } + return el.asJsonObject(); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a long or + * can be coerced to a long, or 0 otherwise. Note that Util represents + * numbers as doubles, so this is lossy; use strings to + * transfer numbers via Util. + */ + public Long optLong(final String name) { + return optLong(name, (long) 0); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a long or + * can be coerced to a long, or {@code fallback} otherwise. Note that Util + * represents numbers as doubles, so this is lossy; use + * strings to transfer numbers via Util. + */ + public Long optLong(final String name, final Long fallback) { + return optLong(name, fallback, true); + } + + public Long optLong(final String name, final Long fallback, + final boolean strict) { + try { + return getLong(name, strict); + } catch (JsonException e) { + return fallback; + } + } + + /** + * Returns the value mapped by {@code name} if it exists, coercing it if + * necessary, or the empty string if no such mapping exists. + */ + public String optString(final String name) { + return optString(name, ""); + } + + /** + * Returns the value mapped by {@code name} if it exists, coercing it if + * necessary, or {@code fallback} if no such mapping exists. + */ + public String optString(final String name, final String fallback) { + return optString(name, fallback, true); + } + + public String optString(final String name, final String fallback, + final boolean strict) { + try { + return getString(name, strict); + } catch (JsonException e) { + return fallback; + } + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value + * mapping with the same name. + * + * @return this object. + */ + public JsonObject put(final String name, final boolean value) + throws JsonException { + return put(name, new JsonBoolean(value)); + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value + * mapping with the same name. + * + * @param value + * a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this object. + */ + public JsonObject put(final String name, final double value) + throws JsonException { + return put(name, new JsonNumber(value)); + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value + * mapping with the same name. + * + * @return this object. + */ + public JsonObject put(final String name, final int value) + throws JsonException { + return put(name, new JsonNumber(value)); + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value + * mapping with the same name. If the value is {@code null}, any existing + * mapping for {@code name} is removed. + * + * @param value + * a {@link JsonObject}, {@link JsonArray}, {@link JsonString}, + * {@link JsonBoolean}, {@link JsonNumber}, {@link JsonNull}. May + * not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this object. + */ + public JsonObject put(final String name, final JsonElement value) + throws JsonException { + checkIfFrozen(); + putInternal(name, value); + return this; + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value + * mapping with the same name. + * + * @return this object. + */ + public JsonObject put(final String name, final long value) + throws JsonException { + return put(name, new JsonNumber(value)); + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value + * mapping with the same name. If the value is {@code null}, any existing + * mapping for {@code name} is removed. + * + * @param value + * a {@link JsonObject}, {@link JsonArray}, String, Boolean, + * Integer, Long, Double, {@code null}. May not be + * {@link Double#isNaN() NaNs} or {@link Double#isInfinite() + * infinities}. + * @return this object. + */ + public JsonObject put(final String name, final Object value) + throws JsonException { + return put(name, wrap(value)); + } + + void putInternal(final String name, JsonElement value) { + if (value == null) { + value = JSON_NULL; + } + nameValuePairs.put(checkName(name), value); + } + + /** + * Equivalent to {@code put(name, value)} when both parameters are non-null; + * does nothing otherwise. + */ + public JsonObject putOpt(final String name, final Object value) + throws JsonException { + if (name == null || value == null) { + return this; + } + return put(name, value); + } + + /** + * Removes the named mapping if it exists; does nothing otherwise. + * + * @return the value previously mapped by {@code name}, or null if there was + * no such mapping. + */ + public Object remove(final String name) { + checkIfFrozen(); + return nameValuePairs.remove(name); + } + + /** + * Returns an array with the values corresponding to {@code names}. The + * array contains null for names that aren't mapped. This method returns + * null if {@code names} is either null or empty. + */ + public JsonArray toJsonArray(final JsonArray names) throws JsonException { + JsonArray result = new JsonArray(); + if (names == null) { + return null; + } + int length = names.length(); + if (length == 0) { + return null; + } + for (int i = 0; i < length; i++) { + String name = Util.toString(names.opt(i)); + result.put(opt(name)); + } + return result; + } + + public Collection valuesSet() { + if (isFrozen()) { + Collections.unmodifiableCollection(nameValuePairs.values()); + } + return nameValuePairs.values(); + } + + @Override + public void write(final JsonWriter writer) throws IOException { + writer.beginObject(); + StringBuilder indent = new StringBuilder(); + + for (Map.Entry e : nameValuePairs.entrySet()) { + + writer.name(indent + e.getKey()); + e.getValue().write(writer); + } + writer.endObject(); + } + } diff --git a/json-core/src/test/java/io/apptik/json/test/FreezingTest.java b/json-core/src/test/java/io/apptik/json/test/FreezingTest.java index 35e0700..598058b 100644 --- a/json-core/src/test/java/io/apptik/json/test/FreezingTest.java +++ b/json-core/src/test/java/io/apptik/json/test/FreezingTest.java @@ -1,319 +1,313 @@ package io.apptik.json.test; - +import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import io.apptik.json.JsonArray; import io.apptik.json.JsonElement; import io.apptik.json.JsonObject; import io.apptik.json.JsonString; -import org.junit.Test; import java.util.ArrayList; -import static junit.framework.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import org.junit.Test; public class FreezingTest { - @Test - public void objectChildrenFreezing() { - JsonObject object = new JsonObject(); - JsonArray childArray = new JsonArray(); - JsonObject childObject = new JsonObject(); - object.put("foo", childArray); - object.put("bar", childObject); - object.freeze(); - try { - childArray.put(true); - fail(); - } catch (IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - - try { - childObject.put("foochild",true); - fail(); - } catch (IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonObject instance.", - ex.getMessage()); - } - - } - - @Test - public void arrayChildrenFreezing() { - JsonArray array = new JsonArray(); - JsonArray childArray = new JsonArray(); - JsonObject childObject = new JsonObject(); - array.put(childArray); - array.put(childObject); - array.freeze(); - try { - childArray.put(true); - fail(); - } catch (IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - - try { - childObject.put("foochild",true); - fail(); - } catch (IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonObject instance.", - ex.getMessage()); - } - - } + @Test + public void arrayChildrenFreezing() { + JsonArray array = new JsonArray(); + JsonArray childArray = new JsonArray(); + JsonObject childObject = new JsonObject(); + array.put(childArray); + array.put(childObject); + array.freeze(); + try { + childArray.put(true); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + childObject.put("foochild", true); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonObject instance.", + ex.getMessage()); + } - @Test - public void objectFreezing() { - JsonObject object = new JsonObject(); - object.put("foo", true); - assertTrue(object.getBoolean("foo")); - object.freeze(); - try { - object.put("bar", 20); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonObject instance.", - ex.getMessage()); - } + } - try { - object.put("bar", 5.5); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonObject instance.", - ex.getMessage()); - } + @Test + public void arrayFreezing() { + JsonArray array = new JsonArray(); + array.put(true); + assertTrue(array.getBoolean(0)); + array.freeze(); + // Adding + try { + array.add(new JsonString("xx")); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + array.add(0, new JsonString("xx")); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } - try { - object.put("bar", null); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonObject instance.", - ex.getMessage()); - } + ArrayList els = new ArrayList(); + els.add(new JsonString("xx")); + els.add(new JsonString("xx")); + try { + array.addAll(els); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + array.addAll(0, els); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + // Removing + try { + array.remove(0); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + array.removeAll(els); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + array.retainAll(els); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + array.clear(); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } - try { - object.put("bar", "xx"); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonObject instance.", - ex.getMessage()); - } + // putting + try { + array.put(0, true); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + array.put(true); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } - try { - object.put("bar", new JsonObject()); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonObject instance.", - ex.getMessage()); - } + try { + array.put(0, 5); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + array.put(5); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + array.put(0, 5.5); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + array.put(5.5); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + array.put(0, null); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + array.put(null); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + array.put(0, "xx"); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + array.put("xx"); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + array.put(0, new JsonObject()); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + array.put(new JsonObject()); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } - try { - object.put("bar", new JsonArray()); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonObject instance.", - ex.getMessage()); - } + try { + array.put(0, new JsonArray()); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + try { + array.put(new JsonArray()); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } + } + @Test + public void objectChildrenFreezing() { + JsonObject object = new JsonObject(); + JsonArray childArray = new JsonArray(); + JsonObject childObject = new JsonObject(); + object.put("foo", childArray); + object.put("bar", childObject); + object.freeze(); + try { + childArray.put(true); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonArray instance.", + ex.getMessage()); + } - try { - object.clear(); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonObject instance.", - ex.getMessage()); - } + try { + childObject.put("foochild", true); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonObject instance.", + ex.getMessage()); + } + } - try { - object.remove("bar"); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonObject instance.", - ex.getMessage()); - } - } + @Test + public void objectFreezing() { + JsonObject object = new JsonObject(); + object.put("foo", true); + assertTrue(object.getBoolean("foo")); + object.freeze(); + try { + object.put("bar", 20); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonObject instance.", + ex.getMessage()); + } + try { + object.put("bar", 5.5); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonObject instance.", + ex.getMessage()); + } - @Test - public void arrayFreezing() { - JsonArray array = new JsonArray(); - array.put(true); - assertTrue(array.getBoolean(0)); - array.freeze(); - //Adding - try { - array.add(new JsonString("xx")); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - try { - array.add(0,new JsonString("xx")); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } + try { + object.put("bar", null); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonObject instance.", + ex.getMessage()); + } - ArrayList els = new ArrayList<>(); - els.add(new JsonString("xx")); - els.add(new JsonString("xx")); - try { - array.addAll(els); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - try { - array.addAll(0,els); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - //Removing - try { - array.remove(0); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - try { - array.removeAll(els); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - try { - array.retainAll(els); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - try { - array.clear(); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } + try { + object.put("bar", "xx"); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonObject instance.", + ex.getMessage()); + } - //putting + try { + object.put("bar", new JsonObject()); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonObject instance.", + ex.getMessage()); + } - try { - array.put(0,true); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - try { - array.put(true); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } + try { + object.put("bar", new JsonArray()); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonObject instance.", + ex.getMessage()); + } - try { - array.put(0,5); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - try { - array.put(5); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - try { - array.put(0,5.5); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - try { - array.put(5.5); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - try { - array.put(0,null); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - try { - array.put(null); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - try { - array.put(0,"xx"); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - try { - array.put("xx"); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - try { - array.put(0,new JsonObject()); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - try { - array.put(new JsonObject()); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } + try { + object.clear(); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonObject instance.", + ex.getMessage()); + } - try { - array.put(0,new JsonArray()); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - try { - array.put(new JsonArray()); - fail(); - } catch(IllegalStateException ex) { - assertEquals("Attempt to modify a frozen JsonArray instance.", - ex.getMessage()); - } - } + try { + object.remove("bar"); + fail(); + } catch (IllegalStateException ex) { + assertEquals("Attempt to modify a frozen JsonObject instance.", + ex.getMessage()); + } + } } diff --git a/json-core/src/test/java/io/apptik/json/test/JsonObjectTest.java b/json-core/src/test/java/io/apptik/json/test/JsonObjectTest.java index d49da69..e4d76d2 100644 --- a/json-core/src/test/java/io/apptik/json/test/JsonObjectTest.java +++ b/json-core/src/test/java/io/apptik/json/test/JsonObjectTest.java @@ -17,11 +17,13 @@ package io.apptik.json.test; -import junit.framework.TestCase; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import static io.apptik.json.JsonNull.JSON_NULL; +import io.apptik.json.JsonArray; +import io.apptik.json.JsonElement; +import io.apptik.json.JsonNumber; +import io.apptik.json.JsonObject; +import io.apptik.json.JsonString; +import io.apptik.json.exception.JsonException; import java.net.ConnectException; import java.util.ArrayList; @@ -36,896 +38,926 @@ import java.util.Set; import java.util.TreeMap; -import io.apptik.json.JsonArray; -import io.apptik.json.JsonNumber; -import io.apptik.json.JsonObject; -import io.apptik.json.JsonString; -import io.apptik.json.exception.JsonException; - -import static io.apptik.json.JsonNull.JSON_NULL; +import junit.framework.TestCase; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; /** - * This black box test was written without inspecting the non-free org.json sourcecode. + * This black box test was written without inspecting the non-free org.json + * sourcecode. */ @RunWith(JUnit4.class) public class JsonObjectTest extends TestCase { - @Test - public void testEmptyObject() throws JsonException { - JsonObject object = new JsonObject(); - assertEquals(0, object.length()); - - // bogus (but documented) behaviour: returns null rather than the empty object! - assertNull(object.names()); - - // returns null rather than an empty array! - assertNull(object.toJsonArray(new JsonArray())); - assertEquals("{}", object.toString()); - try { - object.get("foo"); - fail(); - } catch (JsonException e) { - } - try { - object.getBoolean("foo"); - fail(); - } catch (JsonException e) { - } - try { - object.getDouble("foo"); - fail(); - } catch (JsonException e) { - } - try { - object.getInt("foo"); - fail(); - } catch (JsonException e) { - } - try { - object.getJsonArray("foo"); - fail(); - } catch (JsonException e) { - } - try { - object.getJsonObject("foo"); - fail(); - } catch (JsonException e) { - } - try { - object.getLong("foo"); - fail(); - } catch (JsonException e) { - } - try { - object.getString("foo"); - fail(); - } catch (JsonException e) { - } - assertFalse(object.has("foo")); - assertNull(object.opt("foo")); - assertNull(object.optBoolean("foo")); - assertEquals(Boolean.TRUE, object.optBoolean("foo", Boolean.TRUE)); - assertNull(object.optDouble("foo")); - assertEquals(5.0, object.optDouble("foo", 5.0)); - assertNull(object.optInt("foo")); - assertEquals((Integer)5, object.optInt("foo", 5)); - assertEquals(null, object.optJsonArray("foo")); - assertEquals(null, object.optJsonObject("foo")); - assertNull(object.optLong("foo")); - assertEquals(Long.valueOf(Long.MAX_VALUE-1), object.optLong("foo", Long.MAX_VALUE-1)); - assertNull(object.optString("foo")); // empty string is default! - assertEquals("bar", object.optString("foo", "bar")); - assertNull(object.remove("foo")); - } - - @Test - public void testEqualsAndHashCode() throws JsonException { - JsonObject a = new JsonObject(); - JsonObject b = new JsonObject(); - - // Json object override equals - assertTrue(a.equals(b)); - assertEquals(a.hashCode(), System.identityHashCode(a)); - } - - @Test - public void testGet() throws JsonException { - JsonObject object = new JsonObject(); - Object value = new Object(); - object.put("foo", value); - object.put("bar", new Object()); - object.put("baz", new Object()); - assertEquals(object.get("foo"), value.toString()); - try { - object.get("FOO"); - fail(); - } catch (JsonException e) { - } - try { - object.put(null, value); - fail(); - } catch (JsonException e) { - } - try { - object.get(null); - fail(); - } catch (JsonException e) { - } - } - - @Test - public void testPut() throws JsonException { - JsonObject object = new JsonObject(); - assertSame(object, object.put("foo", Boolean.TRUE)); - object.put("foo", Boolean.FALSE); - assertEquals(Boolean.FALSE, object.getBoolean("foo")); - - object.put("foo", 5.0d); - assertEquals(5.0d, object.getDouble("foo")); - object.put("foo", 0); - assertEquals((Integer)0, object.getInt("foo")); - object.put("bar", Long.MAX_VALUE - 1); - assertEquals((Long)(Long.MAX_VALUE - 1), object.getLong("bar")); - object.put("baz", "x"); - assertEquals("x", object.get("baz").toString()); - object.put("bar", null); - assertEquals(object.get("bar"), null); - } - - @Test - public void testPutNullRemoves() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", "bar"); - object.put("foo", (Collection) null); - assertEquals(1, object.length()); - assertTrue(object.has("foo")); - assertEquals(object.get("foo"), null); - } - - @Test - public void testPutOpt() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", "bar"); - object.putOpt("foo", null); - assertEquals(object.get("foo"), "bar"); - object.putOpt(null, null); - assertEquals(1, object.length()); - object.putOpt(null, "bar"); - assertEquals(1, object.length()); - } - - @Test - public void testPutOptUnsupportedNumbers() throws JsonException { - JsonObject object = new JsonObject(); - try { - object.putOpt("foo", Double.NaN); - fail(); - } catch (IllegalArgumentException e) { - } - try { - object.putOpt("foo", Double.NEGATIVE_INFINITY); - fail(); - } catch (IllegalArgumentException e) { - } - try { - object.putOpt("foo", Double.POSITIVE_INFINITY); - fail(); - } catch (IllegalArgumentException e) { - } - } - - @Test - public void testRemove() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", "bar"); - assertEquals(null, object.remove(null)); - assertEquals(null, object.remove("")); - assertEquals(null, object.remove("bar")); - assertEquals(object.remove("foo"), "bar"); - assertEquals(null, object.remove("foo")); - } - - @Test - public void testBooleans() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", Boolean.TRUE); - object.put("bar", Boolean.FALSE); - object.put("baz", "true"); - object.put("quux", "false"); - assertEquals(4, object.length()); - assertEquals(Boolean.TRUE, object.getBoolean("foo")); - assertEquals(Boolean.FALSE, object.getBoolean("bar")); - assertEquals(Boolean.TRUE, object.getBoolean("baz", Boolean.FALSE)); - assertEquals(Boolean.FALSE, object.getBoolean("quux", Boolean.FALSE)); - assertFalse(object.isNull("foo")); - assertFalse(object.isNull("quux")); - assertTrue(object.has("foo")); - assertTrue(object.has("quux")); - assertFalse(object.has("missing")); - assertEquals(Boolean.TRUE, object.optBoolean("foo")); - assertEquals(Boolean.FALSE, object.optBoolean("bar")); - assertEquals(Boolean.TRUE, object.optBoolean("baz", Boolean.FALSE, Boolean.FALSE)); - assertNull(object.optBoolean("quux")); - assertNull(object.optBoolean("missing")); - assertEquals(Boolean.TRUE, object.optBoolean("foo", Boolean.TRUE)); - assertEquals(Boolean.FALSE, object.optBoolean("bar", Boolean.TRUE)); - assertEquals(Boolean.TRUE, object.optBoolean("baz", Boolean.TRUE)); - assertEquals(Boolean.FALSE, object.optBoolean("quux", Boolean.TRUE, Boolean.FALSE)); - assertEquals(Boolean.TRUE, object.optBoolean("missing", Boolean.TRUE)); - - object.put("foo", "truE"); - object.put("bar", "FALSE"); - assertEquals(Boolean.TRUE, object.getBoolean("foo", Boolean.FALSE)); - assertEquals(Boolean.FALSE, object.getBoolean("bar", Boolean.FALSE)); - assertEquals(Boolean.TRUE, object.optBoolean("foo", Boolean.FALSE, Boolean.FALSE)); - assertEquals(Boolean.FALSE, object.optBoolean("bar", Boolean.TRUE, Boolean.FALSE)); - } - - @Test - public void testCoerceStringToBoolean() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", "maybe"); - try { - object.getBoolean("foo"); - fail(); - } catch (JsonException expected) { - } - assertNull(object.optBoolean("foo")); - assertEquals(Boolean.TRUE, object.optBoolean("foo", Boolean.TRUE)); - } - - @Test - public void testNumbers() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", Double.MIN_VALUE); - object.put("bar", 9223372036854775806L); - object.put("baz", Double.MAX_VALUE); - object.put("quux", -0d); - assertEquals(4, object.length()); - - String toString = object.toString(); - assertTrue(toString, toString.contains("\"foo\":4.9E-324")); - assertTrue(toString, toString.contains("\"bar\":9223372036854775806")); - assertTrue(toString, toString.contains("\"baz\":1.7976931348623157E308")); - - assertTrue(toString, toString.contains("\"quux\":-0.0}") // no trailing decimal point - || toString.contains("\"quux\":-0.0,")); - - assertEquals(object.get("foo"), Double.MIN_VALUE); - assertEquals(object.get("bar"),9223372036854775806L); - assertEquals(object.get("baz"),Double.MAX_VALUE); - assertEquals(object.get("quux"),-0d); - assertEquals(Double.MIN_VALUE, object.getDouble("foo")); - assertEquals(9.223372036854776E18, object.getDouble("bar")); - assertEquals(Double.MAX_VALUE, object.getDouble("baz")); - assertEquals(-0d, object.getDouble("quux")); - assertEquals((Long)0l, object.getLong("foo")); - assertEquals((Long)9223372036854775806L, object.getLong("bar")); - assertEquals((Long)Long.MAX_VALUE, object.getLong("baz")); - assertEquals((Long)0l, object.getLong("quux")); - assertEquals((Integer)0, object.getInt("foo")); - assertEquals((Integer)(-2), object.getInt("bar")); - assertEquals((Integer)Integer.MAX_VALUE, object.getInt("baz")); - assertEquals((Integer)0, object.getInt("quux")); - assertEquals(object.opt("foo"), Double.MIN_VALUE); - assertEquals((Long)9223372036854775806L, object.optLong("bar")); - assertEquals(Double.MAX_VALUE, object.optDouble("baz")); - assertEquals((Integer)0, object.optInt("quux")); - assertEquals(object.opt("foo"),Double.MIN_VALUE); - assertEquals((Long)9223372036854775806L, object.optLong("bar")); - assertEquals(Double.MAX_VALUE, object.optDouble("baz")); - assertEquals((Integer)0, object.optInt("quux")); - assertEquals(Double.MIN_VALUE, object.optDouble("foo", 5.0d)); - assertEquals((Long)9223372036854775806L, object.optLong("bar", 1L)); - assertEquals((Long)Long.MAX_VALUE, object.optLong("baz", 1L)); - assertEquals((Integer)0, object.optInt("quux", -1)); - assertEquals("4.9E-324", object.getString("foo", Boolean.FALSE)); - assertEquals("9223372036854775806", object.getString("bar", Boolean.FALSE)); - assertEquals("1.7976931348623157E308", object.getString("baz", Boolean.FALSE)); - assertEquals("-0.0", object.getString("quux", Boolean.FALSE)); - } - - @Test - public void testFloats() throws JsonException { - JsonObject object = new JsonObject(); - try { - object.put("foo", (Float) Float.NaN); - fail(); - } catch (IllegalArgumentException e) { - } - try { - object.put("foo", (Float) Float.NEGATIVE_INFINITY); - fail(); - } catch (IllegalArgumentException e) { - } - try { - object.put("foo", (Float) Float.POSITIVE_INFINITY); - fail(); - } catch (IllegalArgumentException e) { - } - } - - @Test - public void testOtherNumbers() throws JsonException { - Number nan = new Number() { - public int intValue() { - throw new UnsupportedOperationException(); - } - public long longValue() { - throw new UnsupportedOperationException(); - } - public float floatValue() { - throw new UnsupportedOperationException(); - } - public double doubleValue() { - return Double.NaN; - } - @Override public String toString() { - return "x"; - } - }; - - JsonObject object = new JsonObject(); - try { - object.put("foo", nan); - fail("Object.put() accepted a NaN (via a custom Number class)"); - } catch (IllegalArgumentException e) { - } - } - - @Test - public void testForeignObjects() throws JsonException { - Object foreign = new Object() { - @Override public String toString() { - return "x"; - } - }; - - // foreign object types are accepted and treated as Strings! - JsonObject object = new JsonObject(); - object.put("foo", foreign); - assertEquals("{\"foo\":\"x\"}", object.toString()); - } - - @Test - public void testNullKeys() { - try { - new JsonObject().put(null, Boolean.FALSE); - fail(); - } catch (JsonException e) { - } - try { - new JsonObject().put(null, 0.0d); - fail(); - } catch (JsonException e) { - } - try { - new JsonObject().put(null, 5); - fail(); - } catch (JsonException e) { - } - try { - new JsonObject().put(null, 5L); - fail(); - } catch (JsonException e) { - } - try { - new JsonObject().put(null, "foo"); - fail(); - } catch (JsonException e) { - } - } - - @Test - public void testStrings() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", "true"); - object.put("bar", "5.5"); - object.put("baz", "9223372036854775806"); - object.put("quux", "null"); - object.put("height", "5\"8' tall"); - - assertTrue(object.toString().contains("\"foo\":\"true\"")); - assertTrue(object.toString().contains("\"bar\":\"5.5\"")); - assertTrue(object.toString().contains("\"baz\":\"9223372036854775806\"")); - assertTrue(object.toString().contains("\"quux\":\"null\"")); - assertTrue(object.toString().contains("\"height\":\"5\\\"8' tall\"")); - - assertEquals("true", object.get("foo").toString()); - assertEquals("null", object.getString("quux")); - assertEquals("5\"8' tall", object.getString("height")); - assertEquals("true", object.opt("foo").toString()); - assertEquals("5.5", object.optString("bar")); - assertEquals("true", object.optString("foo", "x")); - assertFalse(object.isNull("foo")); - - assertEquals(Boolean.TRUE, object.optBoolean("foo", Boolean.TRUE, Boolean.FALSE)); - assertNull(object.optInt("foo")); - assertEquals((Integer)(-2), object.optInt("foo", -2)); - - assertEquals(5.5d, object.getDouble("bar", Boolean.FALSE)); - assertEquals((Long)5L, object.getLong("bar", Boolean.FALSE)); - assertEquals((Integer)5, object.getInt("bar", Boolean.FALSE)); - assertEquals((Integer)5, object.optInt("bar", 3, Boolean.FALSE)); - - // The last digit of the string is a 6 but getLong returns a 7. It's probably parsing as a - // double and then converting that to a long. This is consistent with JavaScript. - assertEquals((Long)9223372036854775807L, object.getLong("baz", Boolean.FALSE)); - assertEquals(9.223372036854776E18, object.getDouble("baz", Boolean.FALSE)); - assertEquals((Integer)Integer.MAX_VALUE, object.getInt("baz", Boolean.FALSE)); - - assertFalse(object.isNull("quux")); - try { - object.getDouble("quux"); - fail(); - } catch (JsonException e) { - } - assertNull(object.optDouble("quux")); - assertEquals(-1.0d, object.optDouble("quux", -1.0d)); - - object.put("foo", "TRUE"); - assertEquals(Boolean.TRUE, object.getBoolean("foo", Boolean.FALSE)); - } - - @Test - public void testJsonObjects() throws JsonException { - JsonObject object = new JsonObject(); - - JsonArray a = new JsonArray(); - JsonObject b = new JsonObject(); - object.put("foo", a); - object.put("bar", b); - - assertSame(a, object.getJsonArray("foo")); - assertSame(b, object.getJsonObject("bar")); - try { - object.getJsonObject("foo"); - fail(); - } catch (JsonException e) { - } - try { - object.getJsonArray("bar"); - fail(); - } catch (JsonException e) { - } - assertEquals(a, object.optJsonArray("foo")); - assertEquals(b, object.optJsonObject("bar")); - assertEquals(null, object.optJsonArray("bar")); - assertEquals(null, object.optJsonObject("foo")); - } - - @Test - public void testNullCoercionToString() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", JSON_NULL); - assertEquals("null", object.getString("foo", Boolean.FALSE)); - } - - @Test - public void testArrayCoercion() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", "[Boolean.TRUE]"); - try { - object.getJsonArray("foo"); - fail(); - } catch (JsonException e) { - } - } - - @Test - public void testObjectCoercion() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", "{}"); - try { - object.getJsonObject("foo"); - fail(); - } catch (JsonException e) { - } - } - - @Test - public void testAccumulateValueChecking() throws JsonException { - JsonObject object = new JsonObject(); - try { - object.accumulate("foo", Double.NaN); - fail(); - } catch (IllegalArgumentException e) { - } - object.accumulate("foo", 1); - try { - object.accumulate("foo", Double.NaN); - fail(); - } catch (IllegalArgumentException e) { - } - object.accumulate("foo", 2); - try { - object.accumulate("foo", Double.NaN); - fail(); - } catch (IllegalArgumentException e) { - } - } - - @Test - public void testToJsonArray() throws JsonException { - JsonObject object = new JsonObject(); - Object value = new Object(); - object.put("foo", Boolean.TRUE); - object.put("bar", 5.0d); - object.put("baz", -0.0d); - object.put("quux", value); - - JsonArray names = new JsonArray(); - names.put("baz"); - names.put("quux"); - names.put("foo"); - - JsonArray array = object.toJsonArray(names); - assertEquals(array.get(0), -0.0d); - assertEquals(array.get(1), value.toString()); - assertEquals(array.get(2), Boolean.TRUE); - - object.put("foo", Boolean.FALSE); - assertEquals(array.get(2), Boolean.TRUE); - } - - @Test - public void testToJsonArrayMissingNames() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", Boolean.TRUE); - object.put("bar", 5.0d); - object.put("baz", JSON_NULL); - - JsonArray names = new JsonArray(); - names.put("bar"); - names.put("foo"); - names.put("quux"); - names.put("baz"); - - JsonArray array = object.toJsonArray(names); - assertEquals(3, array.length()); - - assertEquals(array.get(0), 5.0d); - assertEquals(array.get(1), Boolean.TRUE); - assertEquals(array.get(2), null); - } - - @Test - public void testToJsonArrayNull() throws JsonException { - JsonObject object = new JsonObject(); - assertEquals(null, object.toJsonArray(null)); - object.put("foo", 5); - try { - object.toJsonArray(null); - } catch (JsonException e) { - } - } - - @Test - public void testToJsonArrayEndsUpEmpty() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", 5); - JsonArray array = new JsonArray(); - array.put("bar"); - assertEquals(0, object.toJsonArray(array).length()); - } - - @Test - public void testToJsonArrayNonString() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", 5); - object.put("null", 10); - object.put("false", 15); - - JsonArray names = new JsonArray(); - names.put(JSON_NULL); - names.put(Boolean.FALSE); - names.put("foo"); - - // array elements are converted to strings to do name lookups on the map! - JsonArray array = object.toJsonArray(names); - assertEquals(3, array.length()); - assertEquals(array.get(0), 10); - assertEquals(array.get(1), 15); - assertEquals(array.get(2), 5); - } - - @Test - public void testPutUnsupportedNumbers() throws JsonException { - JsonObject object = new JsonObject(); - try { - object.put("foo", Double.NaN); - fail(); - } catch (IllegalArgumentException e) { - } - try { - object.put("foo", Double.NEGATIVE_INFINITY); - fail(); - } catch (IllegalArgumentException e) { - } - try { - object.put("foo", Double.POSITIVE_INFINITY); - fail(); - } catch (IllegalArgumentException e) { - } - } - - @Test - public void testPutUnsupportedNumbersAsObjects() throws JsonException { - JsonObject object = new JsonObject(); - try { - object.put("foo", (Double) Double.NaN); - fail(); - } catch (IllegalArgumentException e) { - } - try { - object.put("foo", (Double) Double.NEGATIVE_INFINITY); - fail(); - } catch (IllegalArgumentException e) { - } - try { - object.put("foo", (Double) Double.POSITIVE_INFINITY); - fail(); - } catch (IllegalArgumentException e) { - } - } - - /** - * JsonObject constructor fails - */ - @Test(expected=IllegalArgumentException.class) - public void testCreateWithUnsupportedNumbers() throws JsonException { - Map contents = new HashMap(); - contents.put("foo", Double.NaN); - contents.put("bar", Double.NEGATIVE_INFINITY); - contents.put("baz", Double.POSITIVE_INFINITY); - - JsonObject object = new JsonObject(contents); - } - - @Test(expected=IllegalArgumentException.class) - public void testToStringWithUnsupportedNumbers() throws JsonException{ - // when the object contains an unsupported number JsonObject constructor fails - JsonObject object = new JsonObject(Collections.singletonMap("foo", Double.NaN)); - } - - @Test - public void testMapConstructorCopiesContents() throws JsonException { - Map contents = new HashMap(); - contents.put("foo", 5); - JsonObject object = new JsonObject(contents); - contents.put("foo", 10); - assertEquals(object.get("foo"), 5); - } - - @Test - public void testMapConstructorWithBogusEntries() { - Map contents = new HashMap(); - contents.put(5, 5); - - try { - new JsonObject(contents); - fail("JsonObject constructor doesn't validate its input!"); - } catch (Exception e) { - } - } - - @Test - public void testAccumulateMutatesInPlace() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", 5); - object.accumulate("foo", 6); - JsonArray array = object.getJsonArray("foo"); - assertEquals("[5,6]", array.toString()); - object.accumulate("foo", 7); - assertEquals("[5,6,7]", array.toString()); - } - - @Test - public void testAccumulateExistingArray() throws JsonException { - JsonArray array = new JsonArray(); - JsonObject object = new JsonObject(); - object.put("foo", array); - object.accumulate("foo", 5); - assertEquals("[5]", array.toString()); - } - - @Test - public void testAccumulatePutArray() throws JsonException { - JsonObject object = new JsonObject(); - object.accumulate("foo", 5); - assertEquals("{\"foo\":5}", object.toString()); - object.accumulate("foo", new JsonArray()); - assertEquals("{\"foo\":[5,[]]}", object.toString()); - } - - @Test - public void testAccumulateNull() { - JsonObject object = new JsonObject(); - try { - object.accumulate(null, 5); - fail(); - } catch (JsonException e) { - } - } - - @Test - public void testEmptyStringKey() throws JsonException { - JsonObject object = new JsonObject(); - object.put("", 5); - assertEquals(object.get(""), 5); - assertEquals("{\"\":5}", object.toString()); - } - - @Test - public void testNullValue() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", JSON_NULL); - object.put("bar", (Collection) null); - - assertTrue(object.has("foo")); - assertTrue(object.has("bar")); - assertTrue(object.isNull("foo")); - assertTrue(object.isNull("bar")); - } - - @Test - public void testHas() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", 5); - assertTrue(object.has("foo")); - assertFalse(object.has("bar")); - assertFalse(object.has(null)); - } - - @Test - public void testOptNull() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", "bar"); - assertEquals(null, object.opt(null)); - assertNull(object.optBoolean(null)); - assertNull(object.optDouble(null)); - assertEquals(null, object.optInt(null)); - assertEquals(null, object.optLong(null)); - assertEquals(null, object.optJsonArray(null)); - assertEquals(null, object.optJsonObject(null)); - assertEquals(null, object.optString(null)); - assertEquals(Boolean.TRUE, object.optBoolean(null, Boolean.TRUE)); - assertEquals(0.0d, object.optDouble(null, 0.0d)); - assertEquals((Integer)1, object.optInt(null, 1)); - assertEquals((Long)1L, object.optLong(null, 1L)); - assertEquals("baz", object.optString(null, "baz")); - } - - @Test - public void testNames() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", 5); - object.put("bar", 6); - object.put("baz", 7); - JsonArray array = object.names(); - assertTrue(array.toString().contains("foo")); - assertTrue(array.toString().contains("bar")); - assertTrue(array.toString().contains("baz")); - } - - @Test - public void testKeysEmptyObject() { - JsonObject object = new JsonObject(); - assertFalse(object.keys().hasNext()); - try { - object.keys().next(); - fail(); - } catch (NoSuchElementException e) { - } - } - - @Test - public void testKeys() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", 5); - object.put("bar", 6); - object.put("foo", 7); - - @SuppressWarnings("unchecked") - Iterator keys = object.keys(); - Set result = new HashSet(); - assertTrue(keys.hasNext()); - result.add(keys.next()); - assertTrue(keys.hasNext()); - result.add(keys.next()); - assertFalse(keys.hasNext()); - assertEquals(new HashSet(Arrays.asList("foo", "bar")), result); - - try { - keys.next(); - fail(); - } catch (NoSuchElementException e) { - } - } - - @Test - public void testMutatingKeysMutatesObject() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", 5); - Iterator keys = object.keys(); - keys.next(); - keys.remove(); - assertEquals(0, object.length()); - } - - @Test - public void testQuote() { - // covered by JsonStringerTest.testEscaping - } - - @Test - public void test_wrap() throws Exception { - assertEquals(JSON_NULL, JsonObject.wrap(null)); - - JsonArray a = new JsonArray(); - assertEquals(a, JsonObject.wrap(a)); - - JsonObject o = new JsonObject(); - assertEquals(o, JsonObject.wrap(o)); - - assertEquals(JSON_NULL, JsonObject.wrap(JSON_NULL)); - - assertTrue(JsonObject.wrap(new byte[0]) instanceof JsonArray); - assertTrue(JsonObject.wrap(new ArrayList()) instanceof JsonArray); - assertTrue(JsonObject.wrap(new HashMap()) instanceof JsonObject); - assertTrue(JsonObject.wrap(Double.valueOf(0)) instanceof JsonNumber); - assertTrue(JsonObject.wrap("hello") instanceof JsonString); - assertTrue(JsonObject.wrap(new ConnectException()) instanceof JsonString); - } - - @Test - public void test_toString_listAsMapValue() throws Exception { - ArrayList list = new ArrayList(); - list.add("a"); - list.add(new ArrayList()); - Map map = new TreeMap(); - map.put("x", "l"); - map.put("y", list); - assertEquals("{\"x\":\"l\",\"y\":[\"a\",[]]}", new JsonObject(map).toString()); - } - - @Test - public void testAppendExistingInvalidKey() throws JsonException { - JsonObject object = new JsonObject(); - object.put("foo", 5); - try { - object.append("foo", 6); - fail(); - } catch (JsonException expected) { - } - } - - @Test - public void testAppendExistingArray() throws JsonException { - JsonArray array = new JsonArray(); - JsonObject object = new JsonObject(); - object.put("foo", array); - object.append("foo", 5); - assertEquals("[5]", array.toString()); - } - - @Test - public void testAppendPutArray() throws JsonException { - JsonObject object = new JsonObject(); - object.append("foo", 5); - assertEquals("{\"foo\":[5]}", object.toString()); - object.append("foo", new JsonArray()); - assertEquals("{\"foo\":[5,[]]}", object.toString()); - } - - @Test - public void testAppendNull() { - JsonObject object = new JsonObject(); - try { - object.append(null, 5); - fail(); - } catch (JsonException e) { - } - } + @Test + public void test_toString_listAsMapValue() throws Exception { + ArrayList list = new ArrayList(); + list.add("a"); + list.add(new ArrayList()); + Map map = new TreeMap(); + map.put("x", "l"); + map.put("y", list); + assertEquals("{\"x\":\"l\",\"y\":[\"a\",[]]}", + new JsonObject(map).toString()); + } + + @Test + public void test_wrap() throws Exception { + assertEquals(JSON_NULL, JsonElement.wrap(null)); + + JsonArray a = new JsonArray(); + assertEquals(a, JsonElement.wrap(a)); + + JsonObject o = new JsonObject(); + assertEquals(o, JsonElement.wrap(o)); + + assertEquals(JSON_NULL, JsonElement.wrap(JSON_NULL)); + + assertTrue(JsonElement.wrap(new byte[0]) instanceof JsonArray); + assertTrue(JsonElement.wrap(new ArrayList()) instanceof JsonArray); + assertTrue(JsonElement.wrap(new HashMap()) instanceof JsonObject); + assertTrue(JsonElement.wrap(Double.valueOf(0)) instanceof JsonNumber); + assertTrue(JsonElement.wrap("hello") instanceof JsonString); + assertTrue(JsonElement.wrap(new ConnectException()) instanceof JsonString); + } + + @Test + public void testAccumulateExistingArray() throws JsonException { + JsonArray array = new JsonArray(); + JsonObject object = new JsonObject(); + object.put("foo", array); + object.accumulate("foo", 5); + assertEquals("[5]", array.toString()); + } + + @Test + public void testAccumulateMutatesInPlace() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", 5); + object.accumulate("foo", 6); + JsonArray array = object.getJsonArray("foo"); + assertEquals("[5,6]", array.toString()); + object.accumulate("foo", 7); + assertEquals("[5,6,7]", array.toString()); + } + + @Test + public void testAccumulateNull() { + JsonObject object = new JsonObject(); + try { + object.accumulate(null, 5); + fail(); + } catch (JsonException e) { + } + } + + @Test + public void testAccumulatePutArray() throws JsonException { + JsonObject object = new JsonObject(); + object.accumulate("foo", 5); + assertEquals("{\"foo\":5}", object.toString()); + object.accumulate("foo", new JsonArray()); + assertEquals("{\"foo\":[5,[]]}", object.toString()); + } + + @Test + public void testAccumulateValueChecking() throws JsonException { + JsonObject object = new JsonObject(); + try { + object.accumulate("foo", Double.NaN); + fail(); + } catch (IllegalArgumentException e) { + } + object.accumulate("foo", 1); + try { + object.accumulate("foo", Double.NaN); + fail(); + } catch (IllegalArgumentException e) { + } + object.accumulate("foo", 2); + try { + object.accumulate("foo", Double.NaN); + fail(); + } catch (IllegalArgumentException e) { + } + } + + @Test + public void testAppendExistingArray() throws JsonException { + JsonArray array = new JsonArray(); + JsonObject object = new JsonObject(); + object.put("foo", array); + object.append("foo", 5); + assertEquals("[5]", array.toString()); + } + + @Test + public void testAppendExistingInvalidKey() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", 5); + try { + object.append("foo", 6); + fail(); + } catch (JsonException expected) { + } + } + + @Test + public void testAppendNull() { + JsonObject object = new JsonObject(); + try { + object.append(null, 5); + fail(); + } catch (JsonException e) { + } + } + + @Test + public void testAppendPutArray() throws JsonException { + JsonObject object = new JsonObject(); + object.append("foo", 5); + assertEquals("{\"foo\":[5]}", object.toString()); + object.append("foo", new JsonArray()); + assertEquals("{\"foo\":[5,[]]}", object.toString()); + } + + @Test + public void testArrayCoercion() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", "[Boolean.TRUE]"); + try { + object.getJsonArray("foo"); + fail(); + } catch (JsonException e) { + } + } + + @Test + public void testBooleans() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", Boolean.TRUE); + object.put("bar", Boolean.FALSE); + object.put("baz", "true"); + object.put("quux", "false"); + assertEquals(4, object.length()); + assertEquals(Boolean.TRUE, object.getBoolean("foo")); + assertEquals(Boolean.FALSE, object.getBoolean("bar")); + assertEquals(Boolean.TRUE, object.getBoolean("baz", Boolean.FALSE)); + assertEquals(Boolean.FALSE, object.getBoolean("quux", Boolean.FALSE)); + assertFalse(object.isNull("foo")); + assertFalse(object.isNull("quux")); + assertTrue(object.has("foo")); + assertTrue(object.has("quux")); + assertFalse(object.has("missing")); + assertEquals(Boolean.TRUE, object.optBoolean("foo")); + assertEquals(Boolean.FALSE, object.optBoolean("bar")); + assertEquals(Boolean.TRUE, + object.optBoolean("baz", Boolean.FALSE, Boolean.FALSE)); + assertFalse(object.optBoolean("quux")); + assertFalse(object.optBoolean("missing")); + assertEquals(Boolean.TRUE, object.optBoolean("foo", Boolean.TRUE)); + assertEquals(Boolean.FALSE, object.optBoolean("bar", Boolean.TRUE)); + assertEquals(Boolean.TRUE, object.optBoolean("baz", Boolean.TRUE)); + assertEquals(Boolean.FALSE, + object.optBoolean("quux", Boolean.TRUE, Boolean.FALSE)); + assertEquals(Boolean.TRUE, object.optBoolean("missing", Boolean.TRUE)); + + object.put("foo", "truE"); + object.put("bar", "FALSE"); + assertEquals(Boolean.TRUE, object.getBoolean("foo", Boolean.FALSE)); + assertEquals(Boolean.FALSE, object.getBoolean("bar", Boolean.FALSE)); + assertEquals(Boolean.TRUE, + object.optBoolean("foo", Boolean.FALSE, Boolean.FALSE)); + assertEquals(Boolean.FALSE, + object.optBoolean("bar", Boolean.TRUE, Boolean.FALSE)); + } + + @Test + public void testCoerceStringToBoolean() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", "maybe"); + try { + object.getBoolean("foo"); + fail(); + } catch (JsonException expected) { + } + assertFalse(object.optBoolean("foo")); + assertEquals(Boolean.TRUE, object.optBoolean("foo", Boolean.TRUE)); + } + + /** + * JsonObject constructor fails + */ + @Test(expected = IllegalArgumentException.class) + public void testCreateWithUnsupportedNumbers() throws JsonException { + Map contents = new HashMap(); + contents.put("foo", Double.NaN); + contents.put("bar", Double.NEGATIVE_INFINITY); + contents.put("baz", Double.POSITIVE_INFINITY); + + JsonObject object = new JsonObject(contents); + } + + @Test + public void testEmptyObject() throws JsonException { + JsonObject object = new JsonObject(); + assertEquals(0, object.length()); + + // bogus (but documented) behaviour: returns null rather than the empty + // object! + assertNull(object.names()); + + // returns null rather than an empty array! + assertNull(object.toJsonArray(new JsonArray())); + assertEquals("{}", object.toString()); + try { + object.get("foo"); + fail(); + } catch (JsonException e) { + } + try { + object.getBoolean("foo"); + fail(); + } catch (JsonException e) { + } + try { + object.getDouble("foo"); + fail(); + } catch (JsonException e) { + } + try { + object.getInt("foo"); + fail(); + } catch (JsonException e) { + } + try { + object.getJsonArray("foo"); + fail(); + } catch (JsonException e) { + } + try { + object.getJsonObject("foo"); + fail(); + } catch (JsonException e) { + } + try { + object.getLong("foo"); + fail(); + } catch (JsonException e) { + } + try { + object.getString("foo"); + fail(); + } catch (JsonException e) { + } + assertFalse(object.has("foo")); + assertNull(object.opt("foo")); + assertFalse(object.optBoolean("foo")); + assertEquals(Boolean.TRUE, object.optBoolean("foo", Boolean.TRUE)); + assertNull(object.optDouble("foo")); + assertEquals(5.0, object.optDouble("foo", 5.0)); + assertEquals(0, (int) object.optInt("foo")); + assertEquals((Integer) 5, object.optInt("foo", 5)); + assertEquals(null, object.optJsonArray("foo")); + assertEquals(null, object.optJsonObject("foo")); + assertEquals((long) 0, (long) object.optLong("foo")); + assertEquals(Long.valueOf(Long.MAX_VALUE - 1), + object.optLong("foo", Long.MAX_VALUE - 1)); + assertEquals("", object.optString("foo")); // empty string is default! + assertEquals("bar", object.optString("foo", "bar")); + assertNull(object.remove("foo")); + } + + @Test + public void testEmptyStringKey() throws JsonException { + JsonObject object = new JsonObject(); + object.put("", 5); + assertEquals(object.get(""), 5); + assertEquals("{\"\":5}", object.toString()); + } + + @Test + public void testEqualsAndHashCode() throws JsonException { + JsonObject a = new JsonObject(); + JsonObject b = new JsonObject(); + + // Json object override equals + assertTrue(a.equals(b)); + assertEquals(a.hashCode(), System.identityHashCode(a)); + } + + @Test + public void testFloats() throws JsonException { + JsonObject object = new JsonObject(); + try { + object.put("foo", (Float) Float.NaN); + fail(); + } catch (IllegalArgumentException e) { + } + try { + object.put("foo", (Float) Float.NEGATIVE_INFINITY); + fail(); + } catch (IllegalArgumentException e) { + } + try { + object.put("foo", (Float) Float.POSITIVE_INFINITY); + fail(); + } catch (IllegalArgumentException e) { + } + } + + @Test + public void testForeignObjects() throws JsonException { + Object foreign = new Object() { + @Override + public String toString() { + return "x"; + } + }; + + // foreign object types are accepted and treated as Strings! + JsonObject object = new JsonObject(); + object.put("foo", foreign); + assertEquals("{\"foo\":\"x\"}", object.toString()); + } + + @Test + public void testGet() throws JsonException { + JsonObject object = new JsonObject(); + Object value = new Object(); + object.put("foo", value); + object.put("bar", new Object()); + object.put("baz", new Object()); + assertEquals(object.get("foo"), value.toString()); + try { + object.get("FOO"); + fail(); + } catch (JsonException e) { + } + try { + object.put(null, value); + fail(); + } catch (JsonException e) { + } + try { + object.get(null); + fail(); + } catch (JsonException e) { + } + } + + @Test + public void testHas() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", 5); + assertTrue(object.has("foo")); + assertFalse(object.has("bar")); + assertFalse(object.has(null)); + } + + @Test + public void testJsonObjects() throws JsonException { + JsonObject object = new JsonObject(); + + JsonArray a = new JsonArray(); + JsonObject b = new JsonObject(); + object.put("foo", a); + object.put("bar", b); + + assertSame(a, object.getJsonArray("foo")); + assertSame(b, object.getJsonObject("bar")); + try { + object.getJsonObject("foo"); + fail(); + } catch (JsonException e) { + } + try { + object.getJsonArray("bar"); + fail(); + } catch (JsonException e) { + } + assertEquals(a, object.optJsonArray("foo")); + assertEquals(b, object.optJsonObject("bar")); + assertEquals(null, object.optJsonArray("bar")); + assertEquals(null, object.optJsonObject("foo")); + } + + @Test + public void testKeys() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", 5); + object.put("bar", 6); + object.put("foo", 7); + + @SuppressWarnings("unchecked") + Iterator keys = object.keys(); + Set result = new HashSet(); + assertTrue(keys.hasNext()); + result.add(keys.next()); + assertTrue(keys.hasNext()); + result.add(keys.next()); + assertFalse(keys.hasNext()); + assertEquals(new HashSet(Arrays.asList("foo", "bar")), result); + + try { + keys.next(); + fail(); + } catch (NoSuchElementException e) { + } + } + + @Test + public void testKeysEmptyObject() { + JsonObject object = new JsonObject(); + assertFalse(object.keys().hasNext()); + try { + object.keys().next(); + fail(); + } catch (NoSuchElementException e) { + } + } + + @Test + public void testMapConstructorCopiesContents() throws JsonException { + Map contents = new HashMap(); + contents.put("foo", 5); + JsonObject object = new JsonObject(contents); + contents.put("foo", 10); + assertEquals(object.get("foo"), 5); + } + + @Test + public void testMapConstructorWithBogusEntries() { + Map contents = new HashMap(); + contents.put(5, 5); + + try { + new JsonObject(contents); + fail("JsonObject constructor doesn't validate its input!"); + } catch (Exception e) { + } + } + + @Test + public void testMutatingKeysMutatesObject() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", 5); + Iterator keys = object.keys(); + keys.next(); + keys.remove(); + assertEquals(0, object.length()); + } + + @Test + public void testNames() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", 5); + object.put("bar", 6); + object.put("baz", 7); + JsonArray array = object.names(); + assertTrue(array.toString().contains("foo")); + assertTrue(array.toString().contains("bar")); + assertTrue(array.toString().contains("baz")); + } + + @Test + public void testNullCoercionToString() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", JSON_NULL); + assertEquals("null", object.getString("foo", Boolean.FALSE)); + } + + @Test + public void testNullKeys() { + try { + new JsonObject().put(null, Boolean.FALSE); + fail(); + } catch (JsonException e) { + } + try { + new JsonObject().put(null, 0.0d); + fail(); + } catch (JsonException e) { + } + try { + new JsonObject().put(null, 5); + fail(); + } catch (JsonException e) { + } + try { + new JsonObject().put(null, 5L); + fail(); + } catch (JsonException e) { + } + try { + new JsonObject().put(null, "foo"); + fail(); + } catch (JsonException e) { + } + } + + @Test + public void testNullValue() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", JSON_NULL); + object.put("bar", (Collection) null); + + assertTrue(object.has("foo")); + assertTrue(object.has("bar")); + assertTrue(object.isNull("foo")); + assertTrue(object.isNull("bar")); + } + + @Test + public void testNumbers() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", Double.MIN_VALUE); + object.put("bar", 9223372036854775806L); + object.put("baz", Double.MAX_VALUE); + object.put("quux", -0d); + assertEquals(4, object.length()); + + String toString = object.toString(); + assertTrue(toString, toString.contains("\"foo\":4.9E-324")); + assertTrue(toString, toString.contains("\"bar\":9223372036854775806")); + assertTrue(toString, + toString.contains("\"baz\":1.7976931348623157E308")); + + assertTrue(toString, toString.contains("\"quux\":-0.0}") // no trailing + // decimal + // point + || toString.contains("\"quux\":-0.0,")); + + assertEquals(object.get("foo"), Double.MIN_VALUE); + assertEquals(object.get("bar"), 9223372036854775806L); + assertEquals(object.get("baz"), Double.MAX_VALUE); + assertEquals(object.get("quux"), -0d); + assertEquals(Double.MIN_VALUE, object.getDouble("foo")); + assertEquals(9.223372036854776E18, object.getDouble("bar")); + assertEquals(Double.MAX_VALUE, object.getDouble("baz")); + assertEquals(-0d, object.getDouble("quux")); + assertEquals((Long) 0l, object.getLong("foo")); + assertEquals((Long) 9223372036854775806L, object.getLong("bar")); + assertEquals((Long) Long.MAX_VALUE, object.getLong("baz")); + assertEquals((Long) 0l, object.getLong("quux")); + assertEquals((Integer) 0, object.getInt("foo")); + assertEquals((Integer) (-2), object.getInt("bar")); + assertEquals((Integer) Integer.MAX_VALUE, object.getInt("baz")); + assertEquals((Integer) 0, object.getInt("quux")); + assertEquals(object.opt("foo"), Double.MIN_VALUE); + assertEquals((Long) 9223372036854775806L, object.optLong("bar")); + assertEquals(Double.MAX_VALUE, object.optDouble("baz")); + assertEquals((Integer) 0, object.optInt("quux")); + assertEquals(object.opt("foo"), Double.MIN_VALUE); + assertEquals((Long) 9223372036854775806L, object.optLong("bar")); + assertEquals(Double.MAX_VALUE, object.optDouble("baz")); + assertEquals((Integer) 0, object.optInt("quux")); + assertEquals(Double.MIN_VALUE, object.optDouble("foo", 5.0d)); + assertEquals((Long) 9223372036854775806L, object.optLong("bar", 1L)); + assertEquals((Long) Long.MAX_VALUE, object.optLong("baz", 1L)); + assertEquals((Integer) 0, object.optInt("quux", -1)); + assertEquals("4.9E-324", object.getString("foo", Boolean.FALSE)); + assertEquals("9223372036854775806", + object.getString("bar", Boolean.FALSE)); + assertEquals("1.7976931348623157E308", + object.getString("baz", Boolean.FALSE)); + assertEquals("-0.0", object.getString("quux", Boolean.FALSE)); + } + + @Test + public void testObjectCoercion() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", "{}"); + try { + object.getJsonObject("foo"); + fail(); + } catch (JsonException e) { + } + } + + @Test + public void testOptNull() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", "bar"); + assertEquals(null, object.opt(null)); + assertFalse(object.optBoolean(null)); + assertNull(object.optDouble(null)); + assertEquals(0, (int) object.optInt(null)); + assertEquals((long) 0, (long) object.optLong(null)); + assertEquals(null, object.optJsonArray(null)); + assertEquals(null, object.optJsonObject(null)); + assertEquals("", object.optString(null)); + assertEquals(Boolean.TRUE, object.optBoolean(null, Boolean.TRUE)); + assertEquals(0.0d, object.optDouble(null, 0.0d)); + assertEquals((Integer) 1, object.optInt(null, 1)); + assertEquals((Long) 1L, object.optLong(null, 1L)); + assertEquals("baz", object.optString(null, "baz")); + } + + @Test + public void testOtherNumbers() throws JsonException { + Number nan = new Number() { + @Override + public double doubleValue() { + return Double.NaN; + } + + @Override + public float floatValue() { + throw new UnsupportedOperationException(); + } + + @Override + public int intValue() { + throw new UnsupportedOperationException(); + } + + @Override + public long longValue() { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return "x"; + } + }; + + JsonObject object = new JsonObject(); + try { + object.put("foo", nan); + fail("Object.put() accepted a NaN (via a custom Number class)"); + } catch (IllegalArgumentException e) { + } + } + + @Test + public void testPut() throws JsonException { + JsonObject object = new JsonObject(); + assertSame(object, object.put("foo", Boolean.TRUE)); + object.put("foo", Boolean.FALSE); + assertEquals(Boolean.FALSE, object.getBoolean("foo")); + + object.put("foo", 5.0d); + assertEquals(5.0d, object.getDouble("foo")); + object.put("foo", 0); + assertEquals((Integer) 0, object.getInt("foo")); + object.put("bar", Long.MAX_VALUE - 1); + assertEquals((Long) (Long.MAX_VALUE - 1), object.getLong("bar")); + object.put("baz", "x"); + assertEquals("x", object.get("baz").toString()); + object.put("bar", null); + assertEquals(object.get("bar"), null); + } + + @Test + public void testPutNullRemoves() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", "bar"); + object.put("foo", (Collection) null); + assertEquals(1, object.length()); + assertTrue(object.has("foo")); + assertEquals(object.get("foo"), null); + } + + @Test + public void testPutOpt() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", "bar"); + object.putOpt("foo", null); + assertEquals(object.get("foo"), "bar"); + object.putOpt(null, null); + assertEquals(1, object.length()); + object.putOpt(null, "bar"); + assertEquals(1, object.length()); + } + + @Test + public void testPutOptUnsupportedNumbers() throws JsonException { + JsonObject object = new JsonObject(); + try { + object.putOpt("foo", Double.NaN); + fail(); + } catch (IllegalArgumentException e) { + } + try { + object.putOpt("foo", Double.NEGATIVE_INFINITY); + fail(); + } catch (IllegalArgumentException e) { + } + try { + object.putOpt("foo", Double.POSITIVE_INFINITY); + fail(); + } catch (IllegalArgumentException e) { + } + } + + @Test + public void testPutUnsupportedNumbers() throws JsonException { + JsonObject object = new JsonObject(); + try { + object.put("foo", Double.NaN); + fail(); + } catch (IllegalArgumentException e) { + } + try { + object.put("foo", Double.NEGATIVE_INFINITY); + fail(); + } catch (IllegalArgumentException e) { + } + try { + object.put("foo", Double.POSITIVE_INFINITY); + fail(); + } catch (IllegalArgumentException e) { + } + } + + @Test + public void testPutUnsupportedNumbersAsObjects() throws JsonException { + JsonObject object = new JsonObject(); + try { + object.put("foo", (Double) Double.NaN); + fail(); + } catch (IllegalArgumentException e) { + } + try { + object.put("foo", (Double) Double.NEGATIVE_INFINITY); + fail(); + } catch (IllegalArgumentException e) { + } + try { + object.put("foo", (Double) Double.POSITIVE_INFINITY); + fail(); + } catch (IllegalArgumentException e) { + } + } + + @Test + public void testQuote() { + // covered by JsonStringerTest.testEscaping + } + + @Test + public void testRemove() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", "bar"); + assertEquals(null, object.remove(null)); + assertEquals(null, object.remove("")); + assertEquals(null, object.remove("bar")); + assertEquals(object.remove("foo"), "bar"); + assertEquals(null, object.remove("foo")); + } + + @Test + public void testStrings() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", "true"); + object.put("bar", "5.5"); + object.put("baz", "9223372036854775806"); + object.put("quux", "null"); + object.put("height", "5\"8' tall"); + + assertTrue(object.toString().contains("\"foo\":\"true\"")); + assertTrue(object.toString().contains("\"bar\":\"5.5\"")); + assertTrue(object.toString() + .contains("\"baz\":\"9223372036854775806\"")); + assertTrue(object.toString().contains("\"quux\":\"null\"")); + assertTrue(object.toString().contains("\"height\":\"5\\\"8' tall\"")); + + assertEquals("true", object.get("foo").toString()); + assertEquals("null", object.getString("quux")); + assertEquals("5\"8' tall", object.getString("height")); + assertEquals("true", object.opt("foo").toString()); + assertEquals("5.5", object.optString("bar")); + assertEquals("true", object.optString("foo", "x")); + assertFalse(object.isNull("foo")); + + assertEquals(Boolean.TRUE, + object.optBoolean("foo", Boolean.TRUE, Boolean.FALSE)); + assertEquals(0, (int) object.optInt("foo")); + assertEquals((Integer) (-2), object.optInt("foo", -2)); + + assertEquals(5.5d, object.getDouble("bar", Boolean.FALSE)); + assertEquals((Long) 5L, object.getLong("bar", Boolean.FALSE)); + assertEquals((Integer) 5, object.getInt("bar", Boolean.FALSE)); + assertEquals((Integer) 5, object.optInt("bar", 3, Boolean.FALSE)); + + // The last digit of the string is a 6 but getLong returns a 7. It's + // probably parsing as a + // double and then converting that to a long. This is consistent with + // JavaScript. + assertEquals((Long) 9223372036854775807L, + object.getLong("baz", Boolean.FALSE)); + assertEquals(9.223372036854776E18, + object.getDouble("baz", Boolean.FALSE)); + assertEquals((Integer) Integer.MAX_VALUE, + object.getInt("baz", Boolean.FALSE)); + + assertFalse(object.isNull("quux")); + try { + object.getDouble("quux"); + fail(); + } catch (JsonException e) { + } + assertNull(object.optDouble("quux")); + assertEquals(-1.0d, object.optDouble("quux", -1.0d)); + + object.put("foo", "TRUE"); + assertEquals(Boolean.TRUE, object.getBoolean("foo", Boolean.FALSE)); + } + + @Test + public void testToJsonArray() throws JsonException { + JsonObject object = new JsonObject(); + Object value = new Object(); + object.put("foo", Boolean.TRUE); + object.put("bar", 5.0d); + object.put("baz", -0.0d); + object.put("quux", value); + + JsonArray names = new JsonArray(); + names.put("baz"); + names.put("quux"); + names.put("foo"); + + JsonArray array = object.toJsonArray(names); + assertEquals(array.get(0), -0.0d); + assertEquals(array.get(1), value.toString()); + assertEquals(array.get(2), Boolean.TRUE); + + object.put("foo", Boolean.FALSE); + assertEquals(array.get(2), Boolean.TRUE); + } + + @Test + public void testToJsonArrayEndsUpEmpty() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", 5); + JsonArray array = new JsonArray(); + array.put("bar"); + assertEquals(0, object.toJsonArray(array).length()); + } + + @Test + public void testToJsonArrayMissingNames() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", Boolean.TRUE); + object.put("bar", 5.0d); + object.put("baz", JSON_NULL); + + JsonArray names = new JsonArray(); + names.put("bar"); + names.put("foo"); + names.put("quux"); + names.put("baz"); + + JsonArray array = object.toJsonArray(names); + assertEquals(3, array.length()); + + assertEquals(array.get(0), 5.0d); + assertEquals(array.get(1), Boolean.TRUE); + assertEquals(array.get(2), null); + } + + @Test + public void testToJsonArrayNonString() throws JsonException { + JsonObject object = new JsonObject(); + object.put("foo", 5); + object.put("null", 10); + object.put("false", 15); + + JsonArray names = new JsonArray(); + names.put(JSON_NULL); + names.put(Boolean.FALSE); + names.put("foo"); + + // array elements are converted to strings to do name lookups on the + // map! + JsonArray array = object.toJsonArray(names); + assertEquals(3, array.length()); + assertEquals(array.get(0), 10); + assertEquals(array.get(1), 15); + assertEquals(array.get(2), 5); + } + + @Test + public void testToJsonArrayNull() throws JsonException { + JsonObject object = new JsonObject(); + assertEquals(null, object.toJsonArray(null)); + object.put("foo", 5); + try { + object.toJsonArray(null); + } catch (JsonException e) { + } + } + + @Test(expected = IllegalArgumentException.class) + public void testToStringWithUnsupportedNumbers() throws JsonException { + // when the object contains an unsupported number JsonObject constructor + // fails + JsonObject object = new JsonObject(Collections.singletonMap("foo", + Double.NaN)); + } } diff --git a/json-generator/.gitignore b/json-generator/.gitignore index 796b96d..c4dfdc5 100644 --- a/json-generator/.gitignore +++ b/json-generator/.gitignore @@ -1 +1,2 @@ /build +/target/ diff --git a/json-generator/build.gradle b/json-generator/build.gradle index f11c72f..272254d 100644 --- a/json-generator/build.gradle +++ b/json-generator/build.gradle @@ -19,14 +19,18 @@ apply plugin: 'jacoco' compileJava { - sourceCompatibility = 1.7 - targetCompatibility = 1.7 + sourceCompatibility = 1.8 + targetCompatibility = 1.8 +} +repositories { + mavenCentral() } - dependencies { + compile 'com.github.mifmif:generex:1.0.2' compile project (':json-core') compile project (':json-wrapper') compile project (':json-schema') + testCompile 'junit:junit:[4,)' } diff --git a/json-generator/src/main/java/io/apptik/json/generator/JsonGenerator.java b/json-generator/src/main/java/io/apptik/json/generator/JsonGenerator.java index 025656e..820bbe8 100644 --- a/json-generator/src/main/java/io/apptik/json/generator/JsonGenerator.java +++ b/json-generator/src/main/java/io/apptik/json/generator/JsonGenerator.java @@ -16,9 +16,9 @@ package io.apptik.json.generator; +import static io.apptik.json.JsonElement.TYPE_ARRAY; +import static io.apptik.json.JsonElement.TYPE_OBJECT; import io.apptik.json.JsonElement; -import io.apptik.json.generator.matcher.SchemaCompositeMatchers; -import io.apptik.json.generator.matcher.SchemaDefMatchers; import io.apptik.json.generator.generators.ArrayGenerator; import io.apptik.json.generator.generators.BooleanGenerator; import io.apptik.json.generator.generators.EnumGenerator; @@ -28,106 +28,119 @@ import io.apptik.json.generator.generators.ObjectGenerator; import io.apptik.json.generator.generators.RangeGenerator; import io.apptik.json.generator.generators.StringGenerator; +import io.apptik.json.generator.matcher.SchemaCompositeMatchers; +import io.apptik.json.generator.matcher.SchemaDefMatchers; import io.apptik.json.schema.Schema; import io.apptik.json.schema.SchemaList; import io.apptik.json.util.LinkedTreeMap; -import org.hamcrest.Matcher; import java.util.Random; -import static io.apptik.json.JsonElement.*; +import org.hamcrest.Matcher; public class JsonGenerator { - protected Schema schema; - protected JsonGeneratorConfig configuration ; - //valid for elements which parent is of type Object - protected String propertyName; - protected static Random rnd = new Random(); - protected static LinkedTreeMap, Class> commonPropertyMatchers; - - //TODO implement custom matchers - static { - commonPropertyMatchers = new LinkedTreeMap, Class>(); - commonPropertyMatchers.put(SchemaDefMatchers.isEnum(), EnumGenerator.class); - commonPropertyMatchers.put(SchemaDefMatchers.isStringType(), StringGenerator.class); - commonPropertyMatchers.put(SchemaDefMatchers.isBooleanType(), BooleanGenerator.class); - commonPropertyMatchers.put(SchemaDefMatchers.isLimitedNumber(), LimitedNumberGenerator.class); - commonPropertyMatchers.put(SchemaDefMatchers.isIntegerType(), IntegerGenerator.class); - commonPropertyMatchers.put(SchemaDefMatchers.isNumberType(), NumberGenerator.class); - commonPropertyMatchers.put(SchemaDefMatchers.isRangeObject(), RangeGenerator.class); - commonPropertyMatchers.put(SchemaDefMatchers.isObjectType(), ObjectGenerator.class); - commonPropertyMatchers.put(SchemaDefMatchers.isArrayType(), ArrayGenerator.class); - - } - - public JsonGenerator(Schema schema, JsonGeneratorConfig configuration) { - this.schema = schema; - this.configuration = configuration; - if(this.configuration==null) { - this.configuration=new JsonGeneratorConfig(); - } - schema.mergeAllRefs(); - mergeComposites(); - - } - - public JsonGenerator(Schema schema, JsonGeneratorConfig configuration, String propertyName) { - this(schema, configuration); - this.propertyName = propertyName; - schema.mergeAllRefs(); - mergeComposites(); - } - - - private void mergeComposites() { - mergeOneOf(); - mergeAllOf(); - mergeAnyOf(); - } - - - //TODO dirty and incorrect for overlaps - private void mergeOneOf() { - if (SchemaCompositeMatchers.hasOneOf().matches(schema)) { - SchemaList list = schema.getOneOf(); - Schema choice = list.get(rnd.nextInt(list.size())); - schema.getJson().remove("oneOf"); - schema.merge(choice); - } - } - - private void mergeAnyOf() { - if(SchemaCompositeMatchers.hasAnyOf().matches(schema)) { - SchemaList list = schema.getAnyOf(); - Schema choice = list.get(rnd.nextInt(list.size())); - schema.getJson().remove("anyOf"); - schema.merge(choice); - } - } - - private void mergeAllOf() { - if (SchemaCompositeMatchers.hasAllOf().matches(schema)) { - SchemaList list = schema.getAllOf(); - schema.getJson().remove("allOf"); - for (Schema subSchema : list) { - schema.merge(subSchema); - } - } - } - - - //TODO make a choice for multi typed elements - public JsonElement generate() { - if(schema.getType().get(0).equals(TYPE_OBJECT)) { - return new ObjectGenerator(schema, configuration).generate(); - } - if(schema.getType().get(0).equals(TYPE_ARRAY)) { - return new ArrayGenerator(schema, configuration).generate(); - } - - - - throw new UnsupportedOperationException("Use Main generator only for full valid JSON object or array."); - } + protected static LinkedTreeMap, Class> commonPropertyMatchers; + protected static Random rnd = new Random(); + // TODO implement custom matchers + static { + commonPropertyMatchers = new LinkedTreeMap, Class>(); + commonPropertyMatchers.put(SchemaDefMatchers.isEnum(), + EnumGenerator.class); + commonPropertyMatchers.put(SchemaDefMatchers.isStringType(), + StringGenerator.class); + commonPropertyMatchers.put(SchemaDefMatchers.isBooleanType(), + BooleanGenerator.class); + commonPropertyMatchers.put(SchemaDefMatchers.isLimitedNumber(), + LimitedNumberGenerator.class); + commonPropertyMatchers.put(SchemaDefMatchers.isIntegerType(), + IntegerGenerator.class); + commonPropertyMatchers.put(SchemaDefMatchers.isNumberType(), + NumberGenerator.class); + commonPropertyMatchers.put(SchemaDefMatchers.isRangeObject(), + RangeGenerator.class); + commonPropertyMatchers.put(SchemaDefMatchers.isObjectType(), + ObjectGenerator.class); + commonPropertyMatchers.put(SchemaDefMatchers.isArrayType(), + ArrayGenerator.class); + + } + protected JsonGeneratorConfig configuration; + // valid for elements which parent is of type Object + protected String propertyName; + + protected Schema schema; + + public JsonGenerator(final Schema schema, + final JsonGeneratorConfig configuration) { + this.schema = schema; + this.configuration = configuration; + if (this.configuration == null) { + this.configuration = new JsonGeneratorConfig(); + } + schema.mergeAllRefs(); + mergeComposites(); + + } + + public JsonGenerator(final Schema schema, + final JsonGeneratorConfig configuration, final String propertyName) { + this(schema, configuration); + this.propertyName = propertyName; + schema.mergeAllRefs(); + mergeComposites(); + } + + // TODO make a choice for multi typed elements + public JsonElement generate() { + if (schema.getType().get(0).equals(TYPE_OBJECT)) { + return new ObjectGenerator(schema, configuration).generate(); + } + if (schema.getType().get(0).equals(TYPE_ARRAY)) { + return new ArrayGenerator(schema, configuration).generate(); + } + + throw new UnsupportedOperationException( + "Use Main generator only for full valid JSON object or array."); + } + + private void mergeAllOf() { + if (SchemaCompositeMatchers.hasAllOf().matches(schema)) { + SchemaList list = schema.getAllOf(); + schema.getJson().remove("allOf"); + for (Schema subSchema : list) { + schema.merge(subSchema); + } + // check if we still another sublevel allOf (manage case of multiple + // allOf in cascade + if (SchemaCompositeMatchers.hasAllOf().matches(schema)) { + mergeAllOf(); + } + } + } + + private void mergeAnyOf() { + if (SchemaCompositeMatchers.hasAnyOf().matches(schema)) { + SchemaList list = schema.getAnyOf(); + Schema choice = list.get(rnd.nextInt(list.size())); + schema.getJson().remove("anyOf"); + schema.merge(choice); + } + } + + private void mergeComposites() { + mergeOneOf(); + mergeAllOf(); + mergeAnyOf(); + } + + // TODO dirty and incorrect for overlaps + private void mergeOneOf() { + if (SchemaCompositeMatchers.hasOneOf().matches(schema)) { + SchemaList list = schema.getOneOf(); + Schema choice = list.get(rnd.nextInt(list.size())); + schema.getJson().remove("oneOf"); + schema.merge(choice); + } + } } diff --git a/json-generator/src/main/java/io/apptik/json/generator/JsonGeneratorConfig.java b/json-generator/src/main/java/io/apptik/json/generator/JsonGeneratorConfig.java index bc4fb24..a9c0b28 100644 --- a/json-generator/src/main/java/io/apptik/json/generator/JsonGeneratorConfig.java +++ b/json-generator/src/main/java/io/apptik/json/generator/JsonGeneratorConfig.java @@ -16,7 +16,6 @@ package io.apptik.json.generator; - import io.apptik.json.JsonElement; import java.util.ArrayList; @@ -25,81 +24,78 @@ public class JsonGeneratorConfig { - //for object type - public HashMap objectPropertiesMin = new HashMap(); - public HashMap objectPropertiesMax = new HashMap(); - public Integer globalObjectPropertiesMin = null; - public Integer globalObjectPropertiesMax = null; - - //for array type - //first check for predefined items, those items can be filled in a random order - public HashMap> arrayPredefinedItems = new HashMap>(); - public HashMap arrayItemsMin = new HashMap(); - public HashMap arrayItemsMax = new HashMap(); - public Integer globalArrayItemsMin = null; - public Integer globalArrayItemsMax = null; - - - //for integer type - public HashMap integerMin = new HashMap(); - public HashMap integerMax = new HashMap(); - public Integer globalIntegerMin = null; - public Integer globalIntegerMax = null; - - //for number type - public HashMap numberMin = new HashMap(); - public HashMap numberMax = new HashMap(); - public Integer globalNumberMin = null; - public Integer globalNumberMax = null; - - //for string type - //first check predefined values, acts as if an enum is set in the schema - public HashMap> stringPredefinedValues = new HashMap>(); - public HashMap stringLengthMin = new HashMap(); - public HashMap stringLengthMax = new HashMap(); - public Integer globalStringLengthMin = null; - public Integer globalStringLengthMax = null; - - //for date and datetime formats, string type - public Date globalDateMin = null; - public Date globalDateMax = null; - public HashMap dateMin = new HashMap(); - public HashMap dateMax = new HashMap(); - - //for uri format, string type - public ArrayList globalUriSchemes = new ArrayList(); - public ArrayList globalUriHosts = new ArrayList(); - public ArrayList globalUriPaths = new ArrayList(); - - public HashMap> uriSchemes = new HashMap>(); - public HashMap> uriHosts = new HashMap>(); - public HashMap> uriPaths = new HashMap>(); - - public HashMap uriPathLengthMin = new HashMap(); - public HashMap uriPathLengthMax = new HashMap(); - public Integer globalUriPathLengthMin = null; - public Integer globalUriPathLengthMax = null; - - - public ArrayList globalEmailHosts = new ArrayList(); - public ArrayList globalEmailLocalParts = new ArrayList(); - - public HashMap> emailHosts = new HashMap>(); - public HashMap> emailLocalParts = new HashMap>(); - - public HashMap emailLocalPartLengthMin = new HashMap(); - public HashMap emailLocalPartLengthMax = new HashMap(); - public Integer globalEmailLocalPartLengthMin = null; - public Integer globalEmailLocalPartLengthMax = null; - - - public HashMap emailHostLengthMin = new HashMap(); - public HashMap emailHostLengthMax = new HashMap(); - public Integer globalEmailHostLengthMin = null; - public Integer globalEmailHostLengthMax = null; - - public ArrayList skipObjectProperties = new ArrayList(); - - + public HashMap arrayItemsMax = new HashMap(); + public HashMap arrayItemsMin = new HashMap(); + // for array type + // first check for predefined items, those items can be filled in a random + // order + public HashMap> arrayPredefinedItems = new HashMap>(); + public HashMap dateMax = new HashMap(); + public HashMap dateMin = new HashMap(); + + public HashMap emailHostLengthMax = new HashMap(); + public HashMap emailHostLengthMin = new HashMap(); + public HashMap> emailHosts = new HashMap>(); + public HashMap emailLocalPartLengthMax = new HashMap(); + public HashMap emailLocalPartLengthMin = new HashMap(); + + public HashMap> emailLocalParts = new HashMap>(); + public boolean emptyJson = false; + public Integer globalArrayItemsMax = null; + public Integer globalArrayItemsMin = null; + + public Date globalDateMax = null; + // for date and datetime formats, string type + public Date globalDateMin = null; + public Integer globalEmailHostLengthMax = null; + public Integer globalEmailHostLengthMin = null; + + public ArrayList globalEmailHosts = new ArrayList(); + public Integer globalEmailLocalPartLengthMax = null; + public Integer globalEmailLocalPartLengthMin = null; + public ArrayList globalEmailLocalParts = new ArrayList(); + public Integer globalIntegerMax = null; + + public Integer globalIntegerMin = null; + public Integer globalNumberMax = null; + public Integer globalNumberMin = null; + public Integer globalObjectPropertiesMax = null; + + public Integer globalObjectPropertiesMin = null; + public Integer globalStringLengthMax = null; + public Integer globalStringLengthMin = null; + + public ArrayList globalUriHosts = new ArrayList(); + public Integer globalUriPathLengthMax = null; + public Integer globalUriPathLengthMin = null; + + public ArrayList globalUriPaths = new ArrayList(); + // for uri format, string type + public ArrayList globalUriSchemes = new ArrayList(); + public HashMap integerMax = new HashMap(); + // for integer type + public HashMap integerMin = new HashMap(); + + public HashMap numberMax = new HashMap(); + // for number type + public HashMap numberMin = new HashMap(); + + public HashMap objectPropertiesMax = new HashMap(); + // for object type + public HashMap objectPropertiesMin = new HashMap(); + + public ArrayList skipObjectProperties = new ArrayList(); + public HashMap stringLengthMax = new HashMap(); + public HashMap stringLengthMin = new HashMap(); + // for string type + // first check predefined values, acts as if an enum is set in the schema + public HashMap> stringPredefinedValues = new HashMap>(); + + public HashMap> uriHosts = new HashMap>(); + public HashMap uriPathLengthMax = new HashMap(); + public HashMap uriPathLengthMin = new HashMap(); + public HashMap> uriPaths = new HashMap>(); + + public HashMap> uriSchemes = new HashMap>(); } diff --git a/json-generator/src/main/java/io/apptik/json/generator/generators/ArrayGenerator.java b/json-generator/src/main/java/io/apptik/json/generator/generators/ArrayGenerator.java index 2136104..8f6da36 100644 --- a/json-generator/src/main/java/io/apptik/json/generator/generators/ArrayGenerator.java +++ b/json-generator/src/main/java/io/apptik/json/generator/generators/ArrayGenerator.java @@ -18,114 +18,150 @@ import io.apptik.json.JsonArray; import io.apptik.json.JsonElement; -import io.apptik.json.generator.JsonGeneratorConfig; import io.apptik.json.generator.JsonGenerator; +import io.apptik.json.generator.JsonGeneratorConfig; import io.apptik.json.schema.Schema; import io.apptik.json.schema.SchemaList; -import org.hamcrest.Matcher; import java.lang.reflect.InvocationTargetException; import java.util.Map; +import org.hamcrest.Matcher; + import sun.reflect.generics.reflectiveObjects.NotImplementedException; public class ArrayGenerator extends JsonGenerator { - public ArrayGenerator(Schema schema, JsonGeneratorConfig configuration) { - super(schema, configuration); - } - - public ArrayGenerator(Schema schema, JsonGeneratorConfig configuration, String propertyName) { - super(schema, configuration, propertyName); - } - - public JsonArray generate() { - JsonArray res = new JsonArray(); - SchemaList items = schema.getItems(); - JsonElement newEl; - int minItems = schema.getMinItems(); - int maxItems = schema.getMaxItems(); - - if(configuration!=null) { - if (configuration.globalArrayItemsMin!=null) minItems = configuration.globalArrayItemsMin; - if (configuration.globalArrayItemsMax!=null) maxItems = configuration.globalArrayItemsMax; - if (propertyName != null ) { - if (configuration.arrayItemsMin.get(propertyName)!=null) minItems = configuration.arrayItemsMin.get(propertyName); - if (configuration.arrayItemsMax.get(propertyName)!=null) maxItems = configuration.arrayItemsMax.get(propertyName); - - } - } - maxItems = minItems + rnd.nextInt(maxItems-minItems); - - - //meant for JSON generator after all, not OutOfMemory generator :) - if(minItems>500) minItems = 500; - if(maxItems>500) maxItems = 500; - - int cnt = 0; - - if(configuration.arrayPredefinedItems != null && propertyName!=null && configuration.arrayPredefinedItems.get(propertyName)!=null) { - for(JsonElement je:configuration.arrayPredefinedItems.get(propertyName)) { - res.put(je); - if(++cnt>maxItems) break; - } - } - else if(items!=null && items.size()>0) { - //if we have array with - if (items.size() == 1) { - for(int i =0;i, Class> entry : commonPropertyMatchers.entrySet()) { - if (entry.getKey().matches(items.get(0))) { - try { - JsonGenerator gen = (JsonGenerator) entry.getValue().getDeclaredConstructor(Schema.class, JsonGeneratorConfig.class, String.class).newInstance(items.get(0), configuration, propertyName); - newEl = gen.generate(); - if (newEl != null) { - res.put(newEl); - break; - } - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - } - } - } - } else { - - for (Schema itemSchema : items) { - for (Map.Entry, Class> entry : commonPropertyMatchers.entrySet()) { - if (entry.getKey().matches(itemSchema)) { - try { - JsonGenerator gen = (JsonGenerator) entry.getValue().getDeclaredConstructor(Schema.class, JsonGeneratorConfig.class).newInstance(itemSchema, configuration); - newEl = gen.generate(); - if (newEl != null) { - res.put(newEl); - break; - } - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } - } - } - if(++cnt>maxItems) break; - } - } - } else { - //then items can be any - throw new NotImplementedException(); - } - - return res; - } + public ArrayGenerator(final Schema schema, + final JsonGeneratorConfig configuration) { + super(schema, configuration); + } + + public ArrayGenerator(final Schema schema, + final JsonGeneratorConfig configuration, final String propertyName) { + super(schema, configuration, propertyName); + } + + @Override + public JsonArray generate() { + JsonArray res = new JsonArray(); + SchemaList items = schema.getItems(); + JsonElement newEl; + int minItems = schema.getMinItems(); + int maxItems = schema.getMaxItems(); + if (configuration != null) { + if (configuration.globalArrayItemsMin != null) { + minItems = configuration.globalArrayItemsMin; + } + if (configuration.globalArrayItemsMax != null) { + maxItems = configuration.globalArrayItemsMax; + } + if (propertyName != null) { + if (configuration.arrayItemsMin.get(propertyName) != null) { + minItems = configuration.arrayItemsMin.get(propertyName); + } + if (configuration.arrayItemsMax.get(propertyName) != null) { + maxItems = configuration.arrayItemsMax.get(propertyName); + } + + } + if (configuration.emptyJson) {// for to create only one tem in the + // array + minItems = 0; + maxItems = 1; + } + } + maxItems = minItems + rnd.nextInt(maxItems - minItems); + + // meant for JSON generator after all, not OutOfMemory generator :) + if (minItems > 500) { + minItems = 500; + } + if (maxItems > 500) { + maxItems = 500; + } + + int cnt = 0; + + if (configuration.arrayPredefinedItems != null && propertyName != null + && configuration.arrayPredefinedItems.get(propertyName) != null) { + for (JsonElement je : configuration.arrayPredefinedItems + .get(propertyName)) { + res.put(je); + if (++cnt > maxItems) { + break; + } + } + } else if (items != null && items.size() > 0) { + // if we have array with + if (items.size() == 1) { + for (int i = 0; i < maxItems; i++) { + for (Map.Entry, Class> entry : commonPropertyMatchers + .entrySet()) { + if (entry.getKey().matches(items.get(0))) { + try { + JsonGenerator gen = (JsonGenerator) entry + .getValue() + .getDeclaredConstructor(Schema.class, + JsonGeneratorConfig.class, + String.class) + .newInstance(items.get(0), + configuration, propertyName); + newEl = gen.generate(); + if (newEl != null) { + res.put(newEl); + break; + } + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + } + } + } else { + + for (Schema itemSchema : items) { + for (Map.Entry, Class> entry : commonPropertyMatchers + .entrySet()) { + if (entry.getKey().matches(itemSchema)) { + try { + JsonGenerator gen = (JsonGenerator) entry + .getValue() + .getDeclaredConstructor(Schema.class, + JsonGeneratorConfig.class) + .newInstance(itemSchema, configuration); + newEl = gen.generate(); + if (newEl != null) { + res.put(newEl); + break; + } + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + } + if (++cnt > maxItems) { + break; + } + } + } + } else { + // then items can be any + throw new NotImplementedException(); + } + + return res; + } } diff --git a/json-generator/src/main/java/io/apptik/json/generator/generators/StringGenerator.java b/json-generator/src/main/java/io/apptik/json/generator/generators/StringGenerator.java index f3dc168..77a8d54 100644 --- a/json-generator/src/main/java/io/apptik/json/generator/generators/StringGenerator.java +++ b/json-generator/src/main/java/io/apptik/json/generator/generators/StringGenerator.java @@ -16,84 +16,151 @@ package io.apptik.json.generator.generators; +import static io.apptik.json.generator.matcher.FormatMatchers.isDateFormat; +import static io.apptik.json.generator.matcher.FormatMatchers.isDateTimeFormat; +import static io.apptik.json.generator.matcher.FormatMatchers.isEmailFormat; +import static io.apptik.json.generator.matcher.FormatMatchers.isTimeFormat; +import static io.apptik.json.generator.matcher.FormatMatchers.isUriFormat; import io.apptik.json.JsonElement; import io.apptik.json.JsonString; import io.apptik.json.generator.JsonGenerator; import io.apptik.json.generator.JsonGeneratorConfig; -import io.apptik.json.generator.generators.formats.*; +import io.apptik.json.generator.generators.formats.DateGenerator; +import io.apptik.json.generator.generators.formats.DateTimeGenerator; +import io.apptik.json.generator.generators.formats.EmailGenerator; +import io.apptik.json.generator.generators.formats.TimeGenerator; +import io.apptik.json.generator.generators.formats.UriGenerator; import io.apptik.json.schema.Schema; import io.apptik.json.util.LinkedTreeMap; -import org.hamcrest.Matcher; import java.lang.reflect.InvocationTargetException; import java.util.Map; -import static io.apptik.json.generator.matcher.FormatMatchers.*; +import org.hamcrest.Matcher; + +import com.mifmif.common.regex.Generex; public class StringGenerator extends JsonGenerator { - protected static LinkedTreeMap, Class> stringFormatMatchers; - - static { - stringFormatMatchers = new LinkedTreeMap, Class>(); - stringFormatMatchers.put(isDateTimeFormat(), DateTimeGenerator.class); - stringFormatMatchers.put(isDateFormat(), DateGenerator.class); - stringFormatMatchers.put(isTimeFormat(), TimeGenerator.class); - stringFormatMatchers.put(isUriFormat(), UriGenerator.class); - stringFormatMatchers.put(isEmailFormat(), EmailGenerator.class); - } - - public StringGenerator(Schema schema, JsonGeneratorConfig configuration) { - super(schema, configuration); - } - - public StringGenerator(Schema schema, JsonGeneratorConfig configuration, String propertyName) { - super(schema, configuration, propertyName); - } - - @Override - public JsonElement generate() { - int minChars = 0; - int maxChars = 15; - if(schema.getFormat()!=null) { - for (Map.Entry, Class> entry : stringFormatMatchers.entrySet()) { - if (entry.getKey().matches(schema)) { - JsonGenerator gen = null; - - try { - gen = (JsonGenerator)entry.getValue().getDeclaredConstructor(Schema.class, JsonGeneratorConfig.class, String.class).newInstance(schema, configuration, propertyName); - return gen.generate(); - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } - - return null; - } - } - } - if(configuration!=null) { - if (configuration.globalStringLengthMin!=null) minChars = configuration.globalStringLengthMin; - if (configuration.globalStringLengthMax!=null) maxChars = configuration.globalStringLengthMax; - if (propertyName != null ) { - if (configuration.stringLengthMin.get(propertyName)!=null) minChars = configuration.stringLengthMin.get(propertyName); - if (configuration.stringLengthMax.get(propertyName)!=null) maxChars = configuration.stringLengthMax.get(propertyName); - - if (configuration.stringPredefinedValues.get(propertyName) != null) { - return new JsonString(configuration.stringPredefinedValues.get(propertyName).get(rnd.nextInt(configuration.stringPredefinedValues.get(propertyName).size()))); - } - } - - } - - String res = ""; - int cnt = minChars + rnd.nextInt(maxChars-minChars); - for(int i=0;i, Class> stringFormatMatchers; + + static { + stringFormatMatchers = new LinkedTreeMap, Class>(); + stringFormatMatchers.put(isDateTimeFormat(), DateTimeGenerator.class); + stringFormatMatchers.put(isDateFormat(), DateGenerator.class); + stringFormatMatchers.put(isTimeFormat(), TimeGenerator.class); + stringFormatMatchers.put(isUriFormat(), UriGenerator.class); + stringFormatMatchers.put(isEmailFormat(), EmailGenerator.class); + } + + public StringGenerator(final Schema schema, + final JsonGeneratorConfig configuration) { + super(schema, configuration); + } + + public StringGenerator(final Schema schema, + final JsonGeneratorConfig configuration, final String propertyName) { + super(schema, configuration, propertyName); + } + + @Override + public JsonElement generate() { + int minChars = 0; + int maxChars = 15; + if (schema.getFormat() != null) { + for (Map.Entry, Class> entry : stringFormatMatchers + .entrySet()) { + if (entry.getKey().matches(schema)) { + JsonGenerator gen = null; + + try { + gen = (JsonGenerator) entry + .getValue() + .getDeclaredConstructor(Schema.class, + JsonGeneratorConfig.class, String.class) + .newInstance(schema, configuration, + propertyName); + return gen.generate(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + + return null; + } + } + } + // check min and max length defined in the schema before else take the + // configuration + int maxLength = this.schema.getMaxLength(); + int minLength = this.schema.getMinLength(); + if (configuration != null) { + // want to restrict the min authorized by the schema + if (configuration.globalStringLengthMin != null) { + minChars = minLength < configuration.globalStringLengthMin ? configuration.globalStringLengthMin + : minLength; + } + // want to restrict the max authorized by the schema + if (configuration.globalStringLengthMax != null) { + maxChars = maxLength > configuration.globalStringLengthMax ? configuration.globalStringLengthMax + : maxLength; + } + if (propertyName != null) { + // want to restrict the min authorized by the schema for this + // property + if (configuration.stringLengthMin.get(propertyName) != null) { + minChars = minLength < configuration.stringLengthMin + .get(propertyName) ? configuration.stringLengthMin + .get(propertyName) : minLength; + } + // want to restrict the max authorized by the schema for this + // property + if (configuration.stringLengthMax.get(propertyName) != null) { + minChars = maxLength < configuration.stringLengthMax + .get(propertyName) ? configuration.stringLengthMax + .get(propertyName) : maxLength; + } + + if (configuration.stringPredefinedValues.get(propertyName) != null) { + return new JsonString( + configuration.stringPredefinedValues + .get(propertyName) + .get(rnd.nextInt(configuration.stringPredefinedValues + .get(propertyName).size()))); + } + } + if (configuration.emptyJson) { + // force to create empty string + minLength = 0; + maxLength = 0; + } + + } + + // manage only pattern and TODO manage pattern properties + String regex = this.schema.getPattern(); + if (regex == null) { + if (maxLength < minLength) { + regex = "\\w{" + minLength + "}"; + } else { + regex = "\\w{" + minLength + "," + maxLength + "}"; + + } + } + Generex wGenerex = new Generex(regex); + + return new JsonString(wGenerex.random()); + + /* + * else { String res = ""; int cnt = minChars + rnd.nextInt(maxChars - + * minChars); for (int i = 0; i < cnt; i++) { res += (rnd.nextBoolean()) + * ? (char) (65 + rnd.nextInt(25)) : (char) (97 + rnd.nextInt(25)); } + * return new JsonString(res); } + */ + } } diff --git a/json-generator/src/main/java/io/apptik/json/generator/matcher/ComparableTypeSafeMatcher.java b/json-generator/src/main/java/io/apptik/json/generator/matcher/ComparableTypeSafeMatcher.java index a353981..ccd739c 100644 --- a/json-generator/src/main/java/io/apptik/json/generator/matcher/ComparableTypeSafeMatcher.java +++ b/json-generator/src/main/java/io/apptik/json/generator/matcher/ComparableTypeSafeMatcher.java @@ -16,14 +16,15 @@ package io.apptik.json.generator.matcher; - import org.hamcrest.Matcher; import org.hamcrest.StringDescription; import org.hamcrest.TypeSafeMatcher; -public abstract class ComparableTypeSafeMatcher extends TypeSafeMatcher implements Comparable> { - @Override - public int compareTo(Matcher another) { - return StringDescription.toString(this).compareTo(StringDescription.toString(another)); - } +public abstract class ComparableTypeSafeMatcher extends TypeSafeMatcher + implements Comparable> { + + public int compareTo(final Matcher another) { + return StringDescription.toString(this).compareTo( + StringDescription.toString(another)); + } } diff --git a/json-generator/src/main/java/io/apptik/json/generator/matcher/FormatMatchers.java b/json-generator/src/main/java/io/apptik/json/generator/matcher/FormatMatchers.java index 2c7247a..200af43 100644 --- a/json-generator/src/main/java/io/apptik/json/generator/matcher/FormatMatchers.java +++ b/json-generator/src/main/java/io/apptik/json/generator/matcher/FormatMatchers.java @@ -16,235 +16,296 @@ package io.apptik.json.generator.matcher; - import io.apptik.json.schema.Schema; + import org.hamcrest.Description; import org.hamcrest.Matcher; public class FormatMatchers { - private FormatMatchers() {} - - public static Matcher isDateFormat() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(!SchemaDefMatchers.isStringType().matches(item)) return false; - if(item.getFormat() == null) return false; - return Schema.FORMAT_DATE.equals(item.getFormat()); - } - - @Override - public void describeTo(Description description) { - description.appendText("is date format"); - } - }; - } - - public static Matcher isDateTimeFormat() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(!SchemaDefMatchers.isStringType().matches(item)) return false; - if(item.getFormat() == null) return false; - return Schema.FORMAT_DATE_TIME.equals(item.getFormat()); - } - - @Override - public void describeTo(Description description) { - description.appendText("is date-time format"); - } - }; - } - - public static Matcher isColorFormat() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(!SchemaDefMatchers.isStringType().matches(item)) return false; - if(item.getFormat() == null) return false; - if(!Schema.FORMAT_COLOR.equals(item.getFormat())) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is color format"); - } - }; - } - - public static Matcher isEmailFormat() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(!SchemaDefMatchers.isStringType().matches(item)) return false; - if(item.getFormat() == null) return false; - if(!item.getFormat().equals(Schema.FORMAT_EMAIL)) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is email format"); - } - }; - } - - public static Matcher isHostnameFormat() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(!SchemaDefMatchers.isStringType().matches(item)) return false; - if(item.getFormat() == null) return false; - if(!item.getFormat().equals(Schema.FORMAT_HOST_NAME) && !item.getFormat().equals(Schema.FORMAT_HOSTNAME)) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is hostname format"); - } - }; - } - - public static Matcher isIPv4Format() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(!SchemaDefMatchers.isStringType().matches(item)) return false; - if(item.getFormat() == null) return false; - if(!item.getFormat().equals(Schema.FORMAT_IP_ADDR) && !item.getFormat().equals(Schema.FORMAT_IPV4)) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is IP v4 address format"); - } - }; - } - - public static Matcher isIPv6Format() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(!SchemaDefMatchers.isStringType().matches(item)) return false; - if(item.getFormat() == null) return false; - if(!item.getFormat().equals(Schema.FORMAT_IPV6)) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is IP v6 address format"); - } - }; - } - - public static Matcher isPhoneFormat() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(!SchemaDefMatchers.isStringType().matches(item)) return false; - if(item.getFormat() == null) return false; - if(!item.getFormat().equals(Schema.FORMAT_PHONE)) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is phone format"); - } - }; - } - - public static Matcher isRegexFormat() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(!SchemaDefMatchers.isStringType().matches(item)) return false; - if(item.getFormat() == null) return false; - if(!item.getFormat().equals(Schema.FORMAT_REGEX)) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is regex format"); - } - }; - } - - public static Matcher isStyleFormat() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(!SchemaDefMatchers.isStringType().matches(item)) return false; - if(item.getFormat() == null) return false; - if(!item.getFormat().equals(Schema.FORMAT_STYLE)) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is style format"); - } - }; - } - - public static Matcher isTimeFormat() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(!SchemaDefMatchers.isStringType().matches(item)) return false; - if(item.getFormat() == null) return false; - if(!item.getFormat().equals(Schema.FORMAT_TIME)) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is time format"); - } - }; - } - - public static Matcher isUriFormat() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(!SchemaDefMatchers.isStringType().matches(item)) return false; - if(item.getFormat() == null) return false; - if(!item.getFormat().equals(Schema.FORMAT_URI)) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is uri format"); - } - }; - } - - public static Matcher isUTCmilisecFormat() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(!SchemaDefMatchers.isStringType().matches(item)) return false; - if(item.getFormat() == null) return false; - if(!item.getFormat().equals(Schema.FORMAT_UTC_MILISEC)) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is utc-millisec format"); - } - }; - } - - - + public static Matcher isColorFormat() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is color format"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (!SchemaDefMatchers.isStringType().matches(item)) { + return false; + } + if (item.getFormat() == null) { + return false; + } + if (!Schema.FORMAT_COLOR.equals(item.getFormat())) { + return false; + } + return true; + } + }; + } + + public static Matcher isDateFormat() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is date format"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (!SchemaDefMatchers.isStringType().matches(item)) { + return false; + } + if (item.getFormat() == null) { + return false; + } + return Schema.FORMAT_DATE.equals(item.getFormat()); + } + }; + } + + public static Matcher isDateTimeFormat() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is date-time format"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (!SchemaDefMatchers.isStringType().matches(item)) { + return false; + } + if (item.getFormat() == null) { + return false; + } + return Schema.FORMAT_DATE_TIME.equals(item.getFormat()); + } + }; + } + + public static Matcher isEmailFormat() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is email format"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (!SchemaDefMatchers.isStringType().matches(item)) { + return false; + } + if (item.getFormat() == null) { + return false; + } + if (!item.getFormat().equals(Schema.FORMAT_EMAIL)) { + return false; + } + return true; + } + }; + } + + public static Matcher isHostnameFormat() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is hostname format"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (!SchemaDefMatchers.isStringType().matches(item)) { + return false; + } + if (item.getFormat() == null) { + return false; + } + if (!item.getFormat().equals(Schema.FORMAT_HOST_NAME) + && !item.getFormat().equals(Schema.FORMAT_HOSTNAME)) { + return false; + } + return true; + } + }; + } + + public static Matcher isIPv4Format() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is IP v4 address format"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (!SchemaDefMatchers.isStringType().matches(item)) { + return false; + } + if (item.getFormat() == null) { + return false; + } + if (!item.getFormat().equals(Schema.FORMAT_IP_ADDR) + && !item.getFormat().equals(Schema.FORMAT_IPV4)) { + return false; + } + return true; + } + }; + } + + public static Matcher isIPv6Format() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is IP v6 address format"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (!SchemaDefMatchers.isStringType().matches(item)) { + return false; + } + if (item.getFormat() == null) { + return false; + } + if (!item.getFormat().equals(Schema.FORMAT_IPV6)) { + return false; + } + return true; + } + }; + } + + public static Matcher isPhoneFormat() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is phone format"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (!SchemaDefMatchers.isStringType().matches(item)) { + return false; + } + if (item.getFormat() == null) { + return false; + } + if (!item.getFormat().equals(Schema.FORMAT_PHONE)) { + return false; + } + return true; + } + }; + } + + public static Matcher isRegexFormat() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is regex format"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (!SchemaDefMatchers.isStringType().matches(item)) { + return false; + } + if (item.getFormat() == null) { + return false; + } + if (!item.getFormat().equals(Schema.FORMAT_REGEX)) { + return false; + } + return true; + } + }; + } + + public static Matcher isStyleFormat() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is style format"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (!SchemaDefMatchers.isStringType().matches(item)) { + return false; + } + if (item.getFormat() == null) { + return false; + } + if (!item.getFormat().equals(Schema.FORMAT_STYLE)) { + return false; + } + return true; + } + }; + } + + public static Matcher isTimeFormat() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is time format"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (!SchemaDefMatchers.isStringType().matches(item)) { + return false; + } + if (item.getFormat() == null) { + return false; + } + if (!item.getFormat().equals(Schema.FORMAT_TIME)) { + return false; + } + return true; + } + }; + } + + public static Matcher isUriFormat() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is uri format"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (!SchemaDefMatchers.isStringType().matches(item)) { + return false; + } + if (item.getFormat() == null) { + return false; + } + if (!item.getFormat().equals(Schema.FORMAT_URI)) { + return false; + } + return true; + } + }; + } + + public static Matcher isUTCmilisecFormat() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is utc-millisec format"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (!SchemaDefMatchers.isStringType().matches(item)) { + return false; + } + if (item.getFormat() == null) { + return false; + } + if (!item.getFormat().equals(Schema.FORMAT_UTC_MILISEC)) { + return false; + } + return true; + } + }; + } + + private FormatMatchers() { + } } diff --git a/json-generator/src/main/java/io/apptik/json/generator/matcher/SchemaCompositeMatchers.java b/json-generator/src/main/java/io/apptik/json/generator/matcher/SchemaCompositeMatchers.java index ed2029b..591ac8f 100644 --- a/json-generator/src/main/java/io/apptik/json/generator/matcher/SchemaCompositeMatchers.java +++ b/json-generator/src/main/java/io/apptik/json/generator/matcher/SchemaCompositeMatchers.java @@ -1,55 +1,61 @@ package io.apptik.json.generator.matcher; - import io.apptik.json.schema.Schema; + import org.hamcrest.Description; import org.hamcrest.Matcher; public class SchemaCompositeMatchers { - private SchemaCompositeMatchers() {} - - public static Matcher hasAnyOf() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(item.getAnyOf() == null || !item.getAnyOf().isEmpty()) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Has anyOf section"); - } - }; - } - public static Matcher hasOneOf() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(item.getOneOf() == null || item.getOneOf().isEmpty()) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Has anyOf section"); - } - }; - } - public static Matcher hasAllOf() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(item.getAllOf() == null || !item.getAllOf().isEmpty()) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Has anyOf section"); - } - }; - } + public static Matcher hasAllOf() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("Has allOf section"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (item.getAllOf() == null || item.getAllOf().isEmpty()) { + return false; + } + return true; + } + }; + } + + public static Matcher hasAnyOf() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("Has anyOf section"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (item.getAnyOf() == null || !item.getAnyOf().isEmpty()) { + return false; + } + return true; + } + }; + } + + public static Matcher hasOneOf() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("Has oneOf section"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (item.getOneOf() == null || item.getOneOf().isEmpty()) { + return false; + } + return true; + } + }; + } + + private SchemaCompositeMatchers() { + } } diff --git a/json-generator/src/main/java/io/apptik/json/generator/matcher/SchemaDefMatchers.java b/json-generator/src/main/java/io/apptik/json/generator/matcher/SchemaDefMatchers.java index 3399e7a..5a75e7e 100644 --- a/json-generator/src/main/java/io/apptik/json/generator/matcher/SchemaDefMatchers.java +++ b/json-generator/src/main/java/io/apptik/json/generator/matcher/SchemaDefMatchers.java @@ -16,190 +16,236 @@ package io.apptik.json.generator.matcher; - +import static io.apptik.json.JsonElement.TYPE_ARRAY; +import static io.apptik.json.JsonElement.TYPE_BOOLEAN; +import static io.apptik.json.JsonElement.TYPE_INTEGER; +import static io.apptik.json.JsonElement.TYPE_NUMBER; +import static io.apptik.json.JsonElement.TYPE_OBJECT; +import static io.apptik.json.JsonElement.TYPE_STRING; import io.apptik.json.schema.Schema; + import org.hamcrest.Description; import org.hamcrest.Matcher; -import static io.apptik.json.JsonElement.*; - public class SchemaDefMatchers { - private SchemaDefMatchers() {} - - public static Matcher isObjectType() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(item.getType() == null) return false; - if(!item.getType().contains(TYPE_OBJECT)) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is Object type"); - } - }; - } - - public static Matcher isArrayType() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(item.getType() == null) return false; - if(!item.getType().contains(TYPE_ARRAY)) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is Array type"); - } - }; - } - - public static Matcher isStringType() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(item.getType() == null) return false; - if(!item.getType().contains(TYPE_STRING)) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is String type"); - } - }; - } - - public static Matcher isNumberType() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(item.getType() == null) return false; - if(!item.getType().contains(TYPE_NUMBER) && !isIntegerType().matches(item)) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is Number type"); - } - }; - } - - public static Matcher isIntegerType() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(item.getType() == null) return false; - if(!item.getType().contains(TYPE_INTEGER)) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is Integer type"); - } - }; - } - - public static Matcher isLimitedNumber() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(!isNumberType().matches(item)) return false; - if(item.getMinimum()==null && item.getMaximum()==null) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is Limited Number"); - } - }; - } - - public static Matcher isBooleanType() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(item.getType() == null) return false; - if(!item.getType().contains(TYPE_BOOLEAN)) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is Boolean type"); - } - }; - } - - public static Matcher isEnum() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(item.getEnum() == null) return false; - if(item.getEnum().length() == 0) return false; - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is enum"); - } - }; - } - - public static Matcher isRangeObject() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - if(!isObjectType().matches(item)) return false; - if(item.getProperties() == null) return false; - if(item.getProperties().length() != 2) return false; - if(item.getProperties().optValue("min") == null) return false; - if(item.getProperties().optValue("max") == null) return false; - if(!isNumberType().matches(item.getProperties().optValue("min"))) return false; - if(!isNumberType().matches(item.getProperties().optValue("max"))) return false; - - - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is Range Object"); - } - }; - } - - public static Matcher isLimitedRangeObject() { - return new ComparableTypeSafeMatcher() { - @Override - protected boolean matchesSafely(Schema item) { - - if(!isRangeObject().matches(item)) return false; - if(Double.compare(item.getProperties().optValue("min").getMinimum(), Double.NaN)==0) return false; - if(Double.compare(item.getProperties().optValue("max").getMaximum(), Double.NaN)==0) return false; - - - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is Limited Range Object"); - } - }; - } - - - - + public static Matcher isArrayType() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is Array type"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (item.getType() == null) { + return false; + } + if (!item.getType().contains(TYPE_ARRAY)) { + return false; + } + return true; + } + }; + } + + public static Matcher isBooleanType() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is Boolean type"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (item.getType() == null) { + return false; + } + if (!item.getType().contains(TYPE_BOOLEAN)) { + return false; + } + return true; + } + }; + } + + public static Matcher isEnum() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is enum"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (item.getEnum() == null) { + return false; + } + if (item.getEnum().length() == 0) { + return false; + } + return true; + } + }; + } + + public static Matcher isIntegerType() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is Integer type"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (item.getType() == null) { + return false; + } + if (!item.getType().contains(TYPE_INTEGER)) { + return false; + } + return true; + } + }; + } + + public static Matcher isLimitedNumber() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is Limited Number"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (!isNumberType().matches(item)) { + return false; + } + if (item.getMinimum() == null && item.getMaximum() == null) { + return false; + } + return true; + } + }; + } + + public static Matcher isLimitedRangeObject() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is Limited Range Object"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + + if (!isRangeObject().matches(item)) { + return false; + } + if (Double.compare(item.getProperties().optValue("min") + .getMinimum(), Double.NaN) == 0) { + return false; + } + if (Double.compare(item.getProperties().optValue("max") + .getMaximum(), Double.NaN) == 0) { + return false; + } + + return true; + } + }; + } + + public static Matcher isNumberType() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is Number type"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (item.getType() == null) { + return false; + } + if (!item.getType().contains(TYPE_NUMBER) + && !isIntegerType().matches(item)) { + return false; + } + return true; + } + }; + } + + public static Matcher isObjectType() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is Object type"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (item.getType() == null) { + return false; + } + if (!item.getType().contains(TYPE_OBJECT)) { + return false; + } + return true; + } + }; + } + + public static Matcher isRangeObject() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is Range Object"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (!isObjectType().matches(item)) { + return false; + } + if (item.getProperties() == null) { + return false; + } + if (item.getProperties().length() != 2) { + return false; + } + if (item.getProperties().optValue("min") == null) { + return false; + } + if (item.getProperties().optValue("max") == null) { + return false; + } + if (!isNumberType().matches( + item.getProperties().optValue("min"))) { + return false; + } + if (!isNumberType().matches( + item.getProperties().optValue("max"))) { + return false; + } + + return true; + } + }; + } + + public static Matcher isStringType() { + return new ComparableTypeSafeMatcher() { + public void describeTo(final Description description) { + description.appendText("is String type"); + } + + @Override + protected boolean matchesSafely(final Schema item) { + if (item.getType() == null) { + return false; + } + if (!item.getType().contains(TYPE_STRING)) { + return false; + } + return true; + } + }; + } + + private SchemaDefMatchers() { + } } diff --git a/json-generator/src/test/java/io/apptik/json/generator/JsonGenerationTest.java b/json-generator/src/test/java/io/apptik/json/generator/JsonGenerationTest.java index 84dc398..99b1a8d 100644 --- a/json-generator/src/test/java/io/apptik/json/generator/JsonGenerationTest.java +++ b/json-generator/src/test/java/io/apptik/json/generator/JsonGenerationTest.java @@ -16,106 +16,114 @@ package io.apptik.json.generator; - +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; import io.apptik.json.JsonElement; import io.apptik.json.JsonObject; import io.apptik.json.schema.SchemaV4; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertTrue; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class JsonGenerationTest { - SchemaV4 schema; - - @Before - public void setUp() throws Exception { - schema = new SchemaV4().wrap(JsonElement.readFrom( - "{\n" + - - - "\"type\" : \"object\"," + - "\"properties\" : {" + - "\"one\" : {\"type\" : \"number\" } ," + - "\"two\" : {\"type\" : \"string\" }," + - "\"three\" : " + "{" + - "\"type\" : \"object\"," + - "\"properties\" : {" + - "\"one\" : {\"type\" : \"number\" } ," + - "\"two\" : {\"type\" : \"string\" }" + - "}" + - "}," + - "\"four\" : {\"type\" : \"boolean\" }," + - "\"five\" : {\"type\" : \"integer\", \"minimum\": 200, \"maximum\":5000 }," + - "\"six\" : {\"enum\" : [\"one\", 2, 3.5, true, [\"almost empty aray\"], {\"one-item\":\"object\"}, null] }, " + - "\"seven\" : {\"type\" : \"string\", \"format\": \"uri\" }," + - "\"eight\" : {\"type\" : \"string\", \"format\": \"email\" }" + - "}" + - "}").asJsonObject()); - } - - @Test - public void testGenerate() throws Exception { - JsonGeneratorConfig gConf = new JsonGeneratorConfig(); - ArrayList images = new ArrayList(); - images.add("/photos/image.jpg"); - images.add("/photos/image.jpg"); - - gConf.uriPaths.put("seven", images); - // gConf.globalUriPaths = images; - JsonObject job = new JsonGenerator(schema, gConf).generate().asJsonObject(); - - System.out.println(job.toString()); - - assertEquals(8, job.length()); - - } - - @Test - public void testLimitedNumber() throws Exception { - JsonGeneratorConfig gConf = new JsonGeneratorConfig(); - gConf.globalIntegerMin = 300; - gConf.globalIntegerMax = 400; - - JsonGenerator g = new JsonGenerator(schema, gConf); - JsonElement el = g.generate(); - JsonObject job = el.asJsonObject(); - System.out.println(job.toString()); - assertTrue(job.getInt("five") >= 300); - assertTrue(job.getInt("five") <= 400); - } - - @Test - public void testEmailTypeString() { - JsonObject job = new JsonGenerator(schema, null).generate().asJsonObject(); - System.out.println(job.toString()); - String emailString = job.get("eight").toString(); - Pattern emailRegex = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{1,10}$", Pattern.CASE_INSENSITIVE); - Matcher matcher = emailRegex.matcher(emailString); - assertTrue(matcher.find()); - } - - @Test - public void testEmailTypeStringLimitedLocal() { - JsonGeneratorConfig gConf = new JsonGeneratorConfig(); - gConf.globalEmailLocalPartLengthMin = 5; - gConf.globalEmailLocalPartLengthMax = 5; - JsonObject job = new JsonGenerator(schema, gConf).generate().asJsonObject(); - System.out.println(job.toString()); - String emailString = job.get("eight").toString(); - Pattern emailRegex = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{1,10}$", Pattern.CASE_INSENSITIVE); - Matcher matcher = emailRegex.matcher(emailString); - assertTrue(matcher.find()); - assertTrue(emailString.split("@")[0].length() >= 5); - assertTrue(emailString.split("@")[0].length() <= 5); - - } + SchemaV4 schema; + + @Before + public void setUp() throws Exception { + schema = new SchemaV4() + .wrap(JsonElement + .readFrom( + "{\n" + + + + "\"type\" : \"object\"," + + "\"properties\" : {" + + "\"one\" : {\"type\" : \"number\" } ," + + "\"two\" : {\"type\" : \"string\" }," + + "\"three\" : " + + "{" + + "\"type\" : \"object\"," + + "\"properties\" : {" + + "\"one\" : {\"type\" : \"number\" } ," + + "\"two\" : {\"type\" : \"string\" }" + + "}" + + "}," + + "\"four\" : {\"type\" : \"boolean\" }," + + "\"five\" : {\"type\" : \"integer\", \"minimum\": 200, \"maximum\":5000 }," + + "\"six\" : {\"enum\" : [\"one\", 2, 3.5, true, [\"almost empty aray\"], {\"one-item\":\"object\"}, null] }, " + + "\"seven\" : {\"type\" : \"string\", \"format\": \"uri\" }," + + "\"eight\" : {\"type\" : \"string\", \"format\": \"email\" }" + + "}" + "}").asJsonObject()); + } + + @Test + public void testEmailTypeString() { + JsonObject job = new JsonGenerator(schema, null).generate() + .asJsonObject(); + System.out.println(job.toString()); + String emailString = job.get("eight").toString(); + Pattern emailRegex = Pattern.compile( + "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{1,10}$", + Pattern.CASE_INSENSITIVE); + Matcher matcher = emailRegex.matcher(emailString); + assertTrue(matcher.find()); + } + + @Test + public void testEmailTypeStringLimitedLocal() { + JsonGeneratorConfig gConf = new JsonGeneratorConfig(); + gConf.globalEmailLocalPartLengthMin = 5; + gConf.globalEmailLocalPartLengthMax = 5; + JsonObject job = new JsonGenerator(schema, gConf).generate() + .asJsonObject(); + System.out.println(job.toString()); + String emailString = job.get("eight").toString(); + Pattern emailRegex = Pattern.compile( + "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{1,10}$", + Pattern.CASE_INSENSITIVE); + Matcher matcher = emailRegex.matcher(emailString); + assertTrue(matcher.find()); + assertTrue(emailString.split("@")[0].length() >= 5); + assertTrue(emailString.split("@")[0].length() <= 5); + + } + + @Test + public void testGenerate() throws Exception { + JsonGeneratorConfig gConf = new JsonGeneratorConfig(); + ArrayList images = new ArrayList(); + images.add("/photos/image.jpg"); + images.add("/photos/image.jpg"); + + gConf.uriPaths.put("seven", images); + // gConf.globalUriPaths = images; + JsonObject job = new JsonGenerator(schema, gConf).generate() + .asJsonObject(); + + System.out.println(job.toString()); + + assertEquals(8, job.length()); + + } + + @Test + public void testLimitedNumber() throws Exception { + JsonGeneratorConfig gConf = new JsonGeneratorConfig(); + gConf.globalIntegerMin = 300; + gConf.globalIntegerMax = 400; + + JsonGenerator g = new JsonGenerator(schema, gConf); + JsonElement el = g.generate(); + JsonObject job = el.asJsonObject(); + System.out.println(job.toString()); + assertTrue(job.getInt("five") >= 300); + assertTrue(job.getInt("five") <= 400); + } } diff --git a/json-schema/src/main/java/io/apptik/json/schema/Schema.java b/json-schema/src/main/java/io/apptik/json/schema/Schema.java index f0199a1..373f424 100644 --- a/json-schema/src/main/java/io/apptik/json/schema/Schema.java +++ b/json-schema/src/main/java/io/apptik/json/schema/Schema.java @@ -1,8 +1,6 @@ package io.apptik.json.schema; - import io.apptik.json.JsonArray; -import io.apptik.json.JsonElement; import io.apptik.json.JsonObject; import io.apptik.json.Validator; import io.apptik.json.schema.fetch.SchemaFetcher; @@ -18,400 +16,425 @@ import java.util.Map; //TODO cleanup -public abstract class Schema extends JsonObjectWrapper implements MetaInfo{ - - public static final String VER_4 = "http://json-schema.org/draft-04/schema#"; - //not yet .. probably not gonna happen .. - public static final String VER_5 = "http://json-schema.org/draft-05/schema#"; - - - //formats as defined in http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23 - //note that there is no definition for the formats in draft-v4 except in http://json-schema.org/latest/json-schema-validation.html#anchor104 - - /** - * date-time This SHOULD be a date in ISO 8601 format of YYYY-MM- - DDThh:mm:ssZ in UTC time. This is the recommended form of date/ - timestamp. - */ - public static final String FORMAT_DATE_TIME = "date-time"; - - /** - * date This SHOULD be a date in the format of YYYY-MM-DD. It is - recommended that you use the "date-time" format instead of "date" - unless you need to transfer only the date part. - */ - public static final String FORMAT_DATE = "date"; - - /** - * - time This SHOULD be a time in the format of hh:mm:ss. It is - recommended that you use the "date-time" format instead of "time" - unless you need to transfer only the time part. - */ - public static final String FORMAT_TIME = "time"; - - /** - * utc-millisec This SHOULD be the difference, measured in - milliseconds, between the specified time and midnight, 00:00 of - January 1, 1970 UTC. The value SHOULD be a number (integer or - float). - */ - public static final String FORMAT_UTC_MILISEC = "utc-millisec"; - - /** - * regex A regular expression, following the regular expression - specification from ECMA 262/Perl 5. - */ - public static final String FORMAT_REGEX = "regex"; - - /** - * color This is a CSS color (like "#FF0000" or "red"), based on CSS - 2.1 [W3C.CR-CSS21-20070719]. - */ - public static final String FORMAT_COLOR = "color"; - - /** - * style This is a CSS style definition (like "color: red; background- - color:#FFF"), based on CSS 2.1 [W3C.CR-CSS21-20070719]. - */ - public static final String FORMAT_STYLE = "style"; - - /** - * phone This SHOULD be a phone number (format MAY follow E.123). - */ - public static final String FORMAT_PHONE = "phone"; - - /** - * uri This value SHOULD be a URI.. - */ - public static final String FORMAT_URI = "uri"; - - /** - * email This SHOULD be an email address. - */ - public static final String FORMAT_EMAIL = "email"; - - - /** - * ip-address This SHOULD be an ip version 4 address. - */ - public static final String FORMAT_IP_ADDR = "ip-address"; //draft v3 - public static final String FORMAT_IPV4 = "ipv4"; //draft v4 - - /** - * ipv6 This SHOULD be an ip version 6 address. - */ - public static final String FORMAT_IPV6 = "ipv6"; - - /** - * - host-name This SHOULD be a host-name. - */ - public static final String FORMAT_HOST_NAME = "host-name"; //draft v3 - public static final String FORMAT_HOSTNAME = "hostname"; //draft v4 - - protected URI origSrc = null; - - protected SchemaFetcher schemaFetcher = null; - - public Schema() { - super(); - this.setContentType("application/schema+json"); - } - - public Schema(URI schemaRef) { - this(); - origSrc = schemaRef; - this.wrap(new SchemaUriFetcher().fetch(origSrc, null, null).getJson()); - } - - public SchemaFetcher getSchemaFetcher() { - return schemaFetcher; - } - - public O setSchemaFetcher(SchemaFetcher schemaFetcher) { - this.schemaFetcher = schemaFetcher; - return (O)this; - } - - public O setOrigSrc(URI origSrc) { - this.origSrc = origSrc; - return (O)this; - } - - public URI getOrigSrc() { - return origSrc; - } - - - /** - * - * @return empty schema from the same version as the current one - */ - public abstract Schema getEmptySchema(String path); - - @Override - public JsonElementWrapper setMetaInfoUri(URI uri) { - throw new RuntimeException("Cannot set Schema on a Schema like this. Use setSchema method."); - } - - @Override - public T wrap(JsonObject jsonElement) { - Schema schema = super.wrap(jsonElement); - mergeWithRef(); - return (T) schema; - } - - public Schema mergeAllRefs(){ - - if(getItems()!=null) { - for (Schema vals : getItems()) { - vals.mergeAllRefs(); - } - } - if(getProperties()!=null) { - for (Map.Entry vals : getProperties()) { - vals.getValue().mergeAllRefs(); - } - } - if(getPatternProperties()!=null) { - for (Map.Entry vals : getPatternProperties()) { - vals.getValue().mergeAllRefs(); - } - } - if(getOneOf()!=null) { - for (Schema vals : getOneOf()) { - vals.mergeAllRefs(); - } - } - if(getAllOf()!=null) { - for (Schema vals : getAllOf()) { - vals.mergeAllRefs(); - } - } - if(getAnyOf()!=null) { - for (Schema vals : getAnyOf()) { - vals.mergeAllRefs(); - } - } - if(getNot()!=null) { - getNot().mergeAllRefs(); - } - - return this; - } - - private void mergeWithRef() { - if(this.getRef()!=null && !this.getRef().trim().isEmpty()) { - //populate values - //if there are title and description already do not change those. - Schema refSchema; - if(schemaFetcher==null) schemaFetcher = new SchemaUriFetcher(); - refSchema = schemaFetcher.fetch(URI.create(this.getRef()), origSrc, URI.create(getId())); - - //TODO not really according to the specs, however specs not really clear what "$ref should precede all other..." means - if (refSchema!=null) { - setOrigSrc(refSchema.origSrc); - setSchemaFetcher(refSchema.getSchemaFetcher()); - //we want to keep title and description for the top schema (these does not affect validation in any way) - String oldTitle = getTitle(); - String oldDesc = getDescription(); - getJson().clear(); - merge(refSchema); - getJson().put("title", oldTitle).put("description", oldDesc); - } - } - } - - @Override - public URI getJsonSchemaUri() { - return URI.create(getSchema()); - } - - public Validator getDefaultValidator() { - return null; - } - - public String getId() { - return getJson().optString("id",""); - } - - /** - * Should be URI - * @param schemaId - * @param - * @return - */ - public O setId(String schemaId) { - getJson().put("id", schemaId); - return (O)this; - } - - protected Schema setSchema(String schemaUri) { - getJson().put("$schema", schemaUri); - return this; - } - - public String getSchema() { - return getJson().optString("$schema",""); - } - - public String getRef() { - return getJson().optString("$ref",""); - } - - public String getTitle() { - return getJson().optString("title", ""); - } - - //TODO validation but optional - public String getFormat() { - return getJson().optString("format", ""); - } - - public String getDescription() { - return getJson().optString("description",""); - } - - public String getDefault() { - return getJson().optString("default",""); - } - - public Double getMultipleOf() { - return getJson().optDouble("multipleOf"); - } - - public Double getMaximum() { - return getJson().optDouble("maximum"); - } - - public boolean getExclusiveMaximum() { - return getJson().optBoolean("exclusiveMaximum", false); - } - - public Double getMinimum() { - return getJson().optDouble("minimum"); - } - - public boolean getExclusiveMinimum() { - return getJson().optBoolean("exclusiveMinimum", false); - } - - public Integer getMaxLength() { - return getJson().optInt("maxLength"); - } - - public Integer getMinLength() { - return getJson().optInt("minLength"); - } - - public String getPattern() { - return getJson().optString("pattern",""); - } - - //TODO can return also object - public boolean getAdditionalItems() { - return getJson().optBoolean("additionalItems", true); - } - - public SchemaList getItems() { - SchemaList res; - if(getJson().opt("items") == null) { - return null; - } - else if(getJson().opt("items").isJsonArray()) { - return new SchemaList(getEmptySchema("items")).wrap(getJson().optJsonArray("items")); - } - else { - res = new SchemaList(getEmptySchema("items")); - res.add((Schema)getEmptySchema("items/0").wrap(getJson().optJsonObject("items"))); - } - return res; - } - - public Integer getMaxItems() { - return getJson().optInt("maxItems"); - } - - public Integer getMinItems() { - return getJson().optInt("minItems"); - } - - public boolean getUniqueItems() { - return getJson().optBoolean("uniqueItems",false); - } - - public Integer getMaxProperties() { - return getJson().optInt("maxProperties"); - } - - public Integer getMinProperties() { - return getJson().optInt("minProperties"); - } - - public List getRequired() { - return new JsonStringArrayWrapper().wrap(getJson().optJsonArray("required")); - } - - //TODO can return also object - public boolean getAdditionalProperties() { - return getJson().optBoolean("additionalProperties", true); - } - - public JsonObject getDefinitions() { - return getJson().optJsonObject("definitions"); - } - - public SchemaMap getProperties() { - if(!getJson().has("properties")) return null; - return new SchemaMap(this.getEmptySchema("properties")).wrap(getJson().optJsonObject("properties")); - } - - public SchemaMap getPatternProperties() { - return new SchemaMap(this.getEmptySchema("patternProperties")).wrap(getJson().optJsonObject("patternProperties")); - } - - public JsonObject getDependencies() { - return getJson().optJsonObject("dependencies"); - } - - public JsonArray getEnum() { - return getJson().optJsonArray("enum"); - } - - public List getType() { - ArrayList res; - if(getJson().opt("type")==null) return null; - if(getJson().opt("type").isJsonArray()) { - return new JsonStringArrayWrapper().wrap(getJson().optJsonArray("type")); - } - else { - res = new ArrayList(); - res.add(getJson().optString("type")); - } - return res; - } - - - //TODO will not pass memebers. use smth similar to SchemaMap - public SchemaList getAllOf() { - if(!getJson().has("allOf")) return null; - return new SchemaList(getEmptySchema("allOf")).wrap(getJson().optJsonArray("allOf")); - } - - public SchemaList getAnyOf() { - if(!getJson().has("anyOf")) return null; - return new SchemaList(getEmptySchema("anyOf")).wrap(getJson().optJsonArray("anyOf")); - } - - public SchemaList getOneOf() { - if(!getJson().has("oneOf")) return null; - return new SchemaList(getEmptySchema("oneOf")).wrap(getJson().optJsonArray("oneOf")); - } - - public Schema getNot() { - if(!getJson().has("not")) return null; - return (Schema)getEmptySchema("not").wrap(getJson().optJsonObject("not")); - } +public abstract class Schema extends JsonObjectWrapper implements MetaInfo { + + /** + * color This is a CSS color (like "#FF0000" or "red"), based on CSS 2.1 + * [W3C.CR-CSS21-20070719]. + */ + public static final String FORMAT_COLOR = "color"; + /** + * date This SHOULD be a date in the format of YYYY-MM-DD. It is recommended + * that you use the "date-time" format instead of "date" unless you need to + * transfer only the date part. + */ + public static final String FORMAT_DATE = "date"; + + // formats as defined in + // http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23 + // note that there is no definition for the formats in draft-v4 except in + // http://json-schema.org/latest/json-schema-validation.html#anchor104 + + /** + * date-time This SHOULD be a date in ISO 8601 format of YYYY-MM- + * DDThh:mm:ssZ in UTC time. This is the recommended form of date/ + * timestamp. + */ + public static final String FORMAT_DATE_TIME = "date-time"; + + /** + * email This SHOULD be an email address. + */ + public static final String FORMAT_EMAIL = "email"; + + /** + * + host-name This SHOULD be a host-name. + */ + public static final String FORMAT_HOST_NAME = "host-name"; // draft v3 + + public static final String FORMAT_HOSTNAME = "hostname"; // draft v4 + + /** + * ip-address This SHOULD be an ip version 4 address. + */ + public static final String FORMAT_IP_ADDR = "ip-address"; // draft v3 + + public static final String FORMAT_IPV4 = "ipv4"; // draft v4 + + /** + * ipv6 This SHOULD be an ip version 6 address. + */ + public static final String FORMAT_IPV6 = "ipv6"; + + /** + * phone This SHOULD be a phone number (format MAY follow E.123). + */ + public static final String FORMAT_PHONE = "phone"; + + /** + * regex A regular expression, following the regular expression + * specification from ECMA 262/Perl 5. + */ + public static final String FORMAT_REGEX = "regex"; + + /** + * style This is a CSS style definition (like "color: red; background- + * color:#FFF"), based on CSS 2.1 [W3C.CR-CSS21-20070719]. + */ + public static final String FORMAT_STYLE = "style"; + + /** + * + time This SHOULD be a time in the format of hh:mm:ss. It is recommended + * that you use the "date-time" format instead of "time" unless you need to + * transfer only the time part. + */ + public static final String FORMAT_TIME = "time"; + /** + * uri This value SHOULD be a URI.. + */ + public static final String FORMAT_URI = "uri"; + + /** + * utc-millisec This SHOULD be the difference, measured in milliseconds, + * between the specified time and midnight, 00:00 of January 1, 1970 UTC. + * The value SHOULD be a number (integer or float). + */ + public static final String FORMAT_UTC_MILISEC = "utc-millisec"; + + public static final String VER_4 = "http://json-schema.org/draft-04/schema#"; + // not yet .. probably not gonna happen .. + public static final String VER_5 = "http://json-schema.org/draft-05/schema#"; + + protected URI origSrc = null; + + protected SchemaFetcher schemaFetcher = null; + + public Schema() { + super(); + this.setContentType("application/schema+json"); + } + + public Schema(final URI schemaRef) { + this(); + origSrc = schemaRef; + this.wrap(new SchemaUriFetcher().fetch(origSrc, null, null).getJson()); + } + + // TODO can return also object + public boolean getAdditionalItems() { + return getJson().optBoolean("additionalItems", true); + } + + // TODO can return also object + public boolean getAdditionalProperties() { + return getJson().optBoolean("additionalProperties", true); + } + + // TODO will not pass memebers. use smth similar to SchemaMap + public SchemaList getAllOf() { + if (!getJson().has("allOf")) { + return null; + } + return new SchemaList(getEmptySchema("allOf")).wrap(getJson() + .optJsonArray("allOf")); + } + + public SchemaList getAnyOf() { + if (!getJson().has("anyOf")) { + return null; + } + return new SchemaList(getEmptySchema("anyOf")).wrap(getJson() + .optJsonArray("anyOf")); + } + + public String getDefault() { + return getJson().optString("default", ""); + } + + public Validator getDefaultValidator() { + return null; + } + + public JsonObject getDefinitions() { + return getJson().optJsonObject("definitions"); + } + + public JsonObject getDependencies() { + return getJson().optJsonObject("dependencies"); + } + + public String getDescription() { + return getJson().optString("description", ""); + } + + /** + * + * @return empty schema from the same version as the current one + */ + public abstract Schema getEmptySchema(String path); + + public JsonArray getEnum() { + return getJson().optJsonArray("enum"); + } + + public boolean getExclusiveMaximum() { + return getJson().optBoolean("exclusiveMaximum", false); + } + + public boolean getExclusiveMinimum() { + return getJson().optBoolean("exclusiveMinimum", false); + } + + // TODO validation but optional + public String getFormat() { + return getJson().optString("format", ""); + } + + public String getId() { + return getJson().optString("id", ""); + } + + public SchemaList getItems() { + SchemaList res; + if (getJson().opt("items") == null) { + return null; + } else if (getJson().opt("items").isJsonArray()) { + return new SchemaList(getEmptySchema("items")).wrap(getJson() + .optJsonArray("items")); + } else { + res = new SchemaList(getEmptySchema("items")); + res.add((Schema) getEmptySchema("items/0").wrap( + getJson().optJsonObject("items"))); + } + return res; + } + + @Override + public URI getJsonSchemaUri() { + return URI.create(getSchema()); + } + + public Double getMaximum() { + return getJson().optDouble("maximum"); + } + + public Integer getMaxItems() { + return getJson().optInt("maxItems", Integer.MAX_VALUE); + } + + public Integer getMaxLength() { + return getJson().optInt("maxLength"); + } + + public Integer getMaxProperties() { + return getJson().optInt("maxProperties"); + } + + public Double getMinimum() { + return getJson().optDouble("minimum"); + } + + public Integer getMinItems() { + return getJson().optInt("minItems"); + } + + public Integer getMinLength() { + return getJson().optInt("minLength"); + } + + public Integer getMinProperties() { + return getJson().optInt("minProperties"); + } + + public Double getMultipleOf() { + return getJson().optDouble("multipleOf"); + } + + public Schema getNot() { + if (!getJson().has("not")) { + return null; + } + return (Schema) getEmptySchema("not").wrap( + getJson().optJsonObject("not")); + } + + public SchemaList getOneOf() { + if (!getJson().has("oneOf")) { + return null; + } + return new SchemaList(getEmptySchema("oneOf")).wrap(getJson() + .optJsonArray("oneOf")); + } + + public URI getOrigSrc() { + return origSrc; + } + + public String getPattern() { + // return a pattern or null empty string is a pattern + return getJson().optString("pattern", null); + } + + public SchemaMap getPatternProperties() { + return new SchemaMap(this.getEmptySchema("patternProperties")) + .wrap(getJson().optJsonObject("patternProperties")); + } + + public SchemaMap getProperties() { + if (!getJson().has("properties")) { + return null; + } + return new SchemaMap(this.getEmptySchema("properties")).wrap(getJson() + .optJsonObject("properties")); + } + + public String getRef() { + return getJson().optString("$ref", ""); + } + + public List getRequired() { + return new JsonStringArrayWrapper().wrap(getJson().optJsonArray( + "required")); + } + + public String getSchema() { + return getJson().optString("$schema", ""); + } + + public SchemaFetcher getSchemaFetcher() { + return schemaFetcher; + } + + public String getTitle() { + return getJson().optString("title", ""); + } + + public List getType() { + ArrayList res; + if (getJson().opt("type") == null) { + return null; + } + if (getJson().opt("type").isJsonArray()) { + return new JsonStringArrayWrapper().wrap(getJson().optJsonArray( + "type")); + } else { + res = new ArrayList(); + res.add(getJson().optString("type")); + } + return res; + } + + public boolean getUniqueItems() { + return getJson().optBoolean("uniqueItems", false); + } + + public Schema mergeAllRefs() { + + if (getItems() != null) { + for (Schema vals : getItems()) { + vals.mergeAllRefs(); + } + } + if (getProperties() != null) { + for (Map.Entry vals : getProperties()) { + vals.getValue().mergeAllRefs(); + } + } + if (getPatternProperties() != null) { + for (Map.Entry vals : getPatternProperties()) { + vals.getValue().mergeAllRefs(); + } + } + if (getOneOf() != null) { + for (Schema vals : getOneOf()) { + vals.mergeAllRefs(); + } + } + if (getAllOf() != null) { + for (Schema vals : getAllOf()) { + vals.mergeAllRefs(); + } + } + if (getAnyOf() != null) { + for (Schema vals : getAnyOf()) { + vals.mergeAllRefs(); + } + } + if (getNot() != null) { + getNot().mergeAllRefs(); + } + + return this; + } + + private void mergeWithRef() { + if (this.getRef() != null && !this.getRef().trim().isEmpty()) { + // populate values + // if there are title and description already do not change those. + Schema refSchema; + if (schemaFetcher == null) { + schemaFetcher = new SchemaUriFetcher(); + } + refSchema = schemaFetcher.fetch(URI.create(this.getRef()), origSrc, + URI.create(getId())); + + // TODO not really according to the specs, however specs not really + // clear what "$ref should precede all other..." means + if (refSchema != null) { + setOrigSrc(refSchema.origSrc); + setSchemaFetcher(refSchema.getSchemaFetcher()); + // we want to keep title and description for the top schema + // (these does not affect validation in any way) + String oldTitle = getTitle(); + String oldDesc = getDescription(); + getJson().clear(); + merge(refSchema); + getJson().put("title", oldTitle).put("description", oldDesc); + } + } + } + + /** + * Should be URI + * + * @param schemaId + * @param + * @return + */ + public O setId(final String schemaId) { + getJson().put("id", schemaId); + return (O) this; + } + + @Override + public JsonElementWrapper setMetaInfoUri(final URI uri) { + throw new RuntimeException( + "Cannot set Schema on a Schema like this. Use setSchema method."); + } + + public O setOrigSrc(final URI origSrc) { + this.origSrc = origSrc; + return (O) this; + } + + protected Schema setSchema(final String schemaUri) { + getJson().put("$schema", schemaUri); + return this; + } + + public O setSchemaFetcher( + final SchemaFetcher schemaFetcher) { + this.schemaFetcher = schemaFetcher; + return (O) this; + } + + @Override + public T wrap(final JsonObject jsonElement) { + Schema schema = super.wrap(jsonElement); + mergeWithRef(); + return (T) schema; + } } diff --git a/json-schema/src/main/java/io/apptik/json/schema/fetch/SchemaResourceFetcher.java b/json-schema/src/main/java/io/apptik/json/schema/fetch/SchemaResourceFetcher.java index 0c164ce..5684b28 100644 --- a/json-schema/src/main/java/io/apptik/json/schema/fetch/SchemaResourceFetcher.java +++ b/json-schema/src/main/java/io/apptik/json/schema/fetch/SchemaResourceFetcher.java @@ -16,7 +16,6 @@ package io.apptik.json.schema.fetch; - import io.apptik.json.JsonElement; import io.apptik.json.exception.JsonException; import io.apptik.json.schema.Schema; @@ -27,39 +26,37 @@ import java.net.URI; import java.net.URL; - //TODO public class SchemaResourceFetcher implements SchemaFetcher { - @Override - public Schema fetch(URI targetUri, URI srcOrigUri, URI srcId) { - Schema res = new SchemaV4(); - final String resource = targetUri.getPath(); + public Schema fetch(final URI targetUri) { + return fetch(targetUri, null, null); + } - URL url = getClass().getClassLoader().getResource(resource); - System.out.println("Fetching res (simpple):" + resource); - System.out.println("Fetching res (full):" + url); - try { + public Schema fetch(final URI targetUri, final URI srcOrigUri, + final URI srcId) { + Schema res = new SchemaV4(); + final String resource = targetUri.getPath(); - res.wrap(JsonElement.readFrom( - new InputStreamReader(getClass().getClassLoader().getResourceAsStream(resource))).asJsonObject()); + URL url = getClass().getClassLoader().getResource(resource); + System.out.println("Fetching res (simpple):" + resource); + System.out.println("Fetching res (full):" + url); + try { - } catch (JsonException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - return res; + res.wrap(JsonElement.readFrom( + new InputStreamReader(getClass().getClassLoader() + .getResourceAsStream(resource))).asJsonObject()); - } + } catch (JsonException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return res; - @Override - public SchemaFetcher withConfig(SchemaFetcherConfig cfg) { - return null; - } + } - @Override - public Schema fetch(URI targetUri) { - return fetch(targetUri, null, null); - } + public SchemaFetcher withConfig(final SchemaFetcherConfig cfg) { + return null; + } } diff --git a/json-schema/src/main/java/io/apptik/json/schema/fetch/SchemaUriFetcher.java b/json-schema/src/main/java/io/apptik/json/schema/fetch/SchemaUriFetcher.java index 6176aaa..713783c 100644 --- a/json-schema/src/main/java/io/apptik/json/schema/fetch/SchemaUriFetcher.java +++ b/json-schema/src/main/java/io/apptik/json/schema/fetch/SchemaUriFetcher.java @@ -16,7 +16,6 @@ package io.apptik.json.schema.fetch; - import io.apptik.json.JsonElement; import io.apptik.json.JsonObject; import io.apptik.json.exception.JsonException; @@ -28,100 +27,107 @@ import java.net.URI; import java.net.URISyntaxException; - public class SchemaUriFetcher implements SchemaFetcher { - private SchemaFetcherConfig cfg; - - - //TODO use config to set to use id instead of orig source - public URI resolveUri(URI targetUri, URI srcOrigUri, URI srcId) { - if(targetUri.isAbsolute()) { - return targetUri; - } else if(srcOrigUri==null || !srcOrigUri.isAbsolute()) { - return targetUri; - } else { - return srcOrigUri.resolve(targetUri); - } - } - - public URI convertUri(URI schemaUri) { - if(cfg==null) return schemaUri; - URI res = null; - - String scheme = schemaUri.getScheme(); - String authority = schemaUri.getAuthority(); - String path = schemaUri.getPath(); - String query = schemaUri.getQuery(); - - if(cfg.uriSchemeReplacements.containsKey(scheme)) { - scheme = cfg.uriSchemeReplacements.get(scheme); - } - - if(cfg.uriAuthorityReplacements.containsKey(authority)) { - authority = cfg.uriAuthorityReplacements.get(authority); - } - - if(cfg.uriPathReplacements.containsKey(path)) { - path = cfg.uriPathReplacements.get(path); - } - - if(cfg.uriQueryReplacements.containsKey(query)) { - query = cfg.uriQueryReplacements.get(query); - } - - try { - res = new URI(scheme, authority, path, query, schemaUri.getFragment()); - } catch (URISyntaxException e) { - e.printStackTrace(); - } - return res; - } - - - @Override - public Schema fetch(URI targetUri) { - return fetch(targetUri, null, null); - } - - //accepts only absolute URI or converted absolute URI - @Override - public Schema fetch(URI targetUri, URI srcOrigUri, URI srcId) { - Schema res = null; - - URI schemaUri = convertUri(resolveUri(targetUri, srcOrigUri, srcId)); - if(!schemaUri.isAbsolute()) throw new RuntimeException("Json Schema Fetcher works only with absolute URIs"); - try { - String fragment = schemaUri.getFragment(); - JsonObject schemaJson = JsonElement.readFrom(new InputStreamReader(schemaUri.toURL().openStream())).asJsonObject(); - if(fragment!=null && !fragment.trim().isEmpty()) { - String[] pointers = fragment.split("/"); - for (String pointer : pointers) { - if (pointer != null && !pointer.trim().isEmpty()) { - schemaJson = schemaJson.getJsonObject(pointer); - - } - } - } - - String version = schemaJson.optString("$schema",""); - if(version.equals(Schema.VER_4)) { - res = new SchemaV4().setSchemaFetcher(this).setOrigSrc(schemaUri).wrap(schemaJson); - } else { - res = new SchemaV4().setSchemaFetcher(this).setOrigSrc(schemaUri).wrap(schemaJson); - } - - } catch (IOException e) { - e.printStackTrace(); - } catch (JsonException e) { - e.printStackTrace(); - } - return res; - } - - @Override - public SchemaFetcher withConfig(SchemaFetcherConfig cfg) { - this.cfg = cfg; - return this; - } + private SchemaFetcherConfig cfg; + + public URI convertUri(final URI schemaUri) { + if (cfg == null) { + return schemaUri; + } + URI res = null; + + String scheme = schemaUri.getScheme(); + String authority = schemaUri.getAuthority(); + String path = schemaUri.getPath(); + String query = schemaUri.getQuery(); + + if (cfg.uriSchemeReplacements.containsKey(scheme)) { + scheme = cfg.uriSchemeReplacements.get(scheme); + } + + if (cfg.uriAuthorityReplacements.containsKey(authority)) { + authority = cfg.uriAuthorityReplacements.get(authority); + } + + if (cfg.uriPathReplacements.containsKey(path)) { + path = cfg.uriPathReplacements.get(path); + } + + if (cfg.uriQueryReplacements.containsKey(query)) { + query = cfg.uriQueryReplacements.get(query); + } + + try { + res = new URI(scheme, authority, path, query, + schemaUri.getFragment()); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return res; + } + + public Schema fetch(final URI targetUri) { + return fetch(targetUri, null, null); + } + + public Schema fetch(final URI targetUri, final URI srcOrigUri, + final URI srcId) { + Schema res = null; + + URI schemaUri = convertUri(resolveUri(targetUri, srcOrigUri, srcId)); + if (!schemaUri.isAbsolute()) { + throw new RuntimeException( + "Json Schema Fetcher works only with absolute URIs"); + } + try { + String fragment = schemaUri.getFragment(); + JsonObject schemaJson = JsonElement.readFrom( + new InputStreamReader(schemaUri.toURL().openStream())) + .asJsonObject(); + if (fragment != null && !fragment.trim().isEmpty()) { + String[] pointers = fragment.split("/"); + for (String pointer : pointers) { + if (pointer != null && !pointer.trim().isEmpty()) { + schemaJson = schemaJson.getJsonObject(pointer); + + } + } + } + + String version = schemaJson.optString("$schema", ""); + if (version.equals(Schema.VER_4)) { + res = new SchemaV4().setSchemaFetcher(this) + .setOrigSrc(schemaUri).wrap(schemaJson); + } else { + res = new SchemaV4().setSchemaFetcher(this) + .setOrigSrc(schemaUri).wrap(schemaJson); + } + + } catch (IOException e) { + e.printStackTrace(); + } catch (JsonException e) { + e.printStackTrace(); + } + return res; + } + + // accepts only absolute URI or converted absolute URI + + // TODO use config to set to use id instead of orig source + public URI resolveUri(final URI targetUri, final URI srcOrigUri, + final URI srcId) { + if (targetUri.isAbsolute()) { + return targetUri; + } else if (srcOrigUri == null || !srcOrigUri.isAbsolute()) { + return targetUri; + } else { + return srcOrigUri.resolve(targetUri); + } + } + + public SchemaFetcher withConfig(final SchemaFetcherConfig cfg) { + this.cfg = cfg; + return this; + } } diff --git a/json-schema/src/main/java/io/apptik/json/schema/validation/CommonMatchers.java b/json-schema/src/main/java/io/apptik/json/schema/validation/CommonMatchers.java index 44fdfa3..6993a53 100644 --- a/json-schema/src/main/java/io/apptik/json/schema/validation/CommonMatchers.java +++ b/json-schema/src/main/java/io/apptik/json/schema/validation/CommonMatchers.java @@ -16,627 +16,755 @@ package io.apptik.json.schema.validation; - +import static io.apptik.json.JsonElement.TYPE_INTEGER; +import static io.apptik.json.JsonElement.TYPE_NUMBER; import io.apptik.json.JsonArray; import io.apptik.json.JsonElement; import io.apptik.json.Validator; -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.hamcrest.TypeSafeDiagnosingMatcher; -import java.util.*; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.regex.Pattern; -import static io.apptik.json.JsonElement.*; - +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeDiagnosingMatcher; public class CommonMatchers { - private CommonMatchers() { - } - - - // ==> STRING ==> - public static Matcher withCharsLessOrEqualTo(final int value) { - return new TypeSafeDiagnosingMatcher() { - - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not String - if (!item.isString()) return true; - if (item.asString().length() > value) { - mismatchDescription.appendText("String length more than maximum value: " + value); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("String maximum length"); - } - }; - } - - public static Matcher withCharsMoreOrEqualTo(final int value) { - return new TypeSafeDiagnosingMatcher() { - - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not String - if (!item.isString()) return true; - if (item.asString().length() < value) { - mismatchDescription.appendText("String length less than minimum value: " + value); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("String minimum length"); - } - }; - } - - public static Matcher matchesPattern(final String value) { - return new TypeSafeDiagnosingMatcher() { - - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not String - if (!item.isString()) return true; - if (!Pattern.matches(value, item.asString())) { - mismatchDescription.appendText("Pattern '" + value + "' does not match '" + item.asString() + "'"); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("Pattern match"); - } - }; - } - - // <== STRING <== - - - // ==> NUMBER ==> - - public static Matcher isLessThan(final double value) { - return new TypeSafeDiagnosingMatcher() { - - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not Number - if (!item.isNumber()) return true; - if (!(item.asDouble() < value)) { - mismatchDescription.appendText("value is not less than exclusive maximum " + value); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("exclusive maximum"); - } - }; - } - - public static Matcher isLessOrEqualThan(final double value) { - return new TypeSafeDiagnosingMatcher() { - - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not Number - if (!item.isNumber()) return true; - if (!(item.asDouble() <= value)) { - mismatchDescription.appendText("value is not less than maximum " + value); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("maximum"); - } - }; - } - - public static Matcher isMoreThan(final double value) { - return new TypeSafeDiagnosingMatcher() { - - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not Number - if (!item.isNumber()) return true; - if (!(item.asDouble() > value)) { - mismatchDescription.appendText("value is not more than exclusive minimum " + value); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("exclusive minimum"); - } - }; - } - - - public static Matcher isMoreOrEqualThan(final double value) { - return new TypeSafeDiagnosingMatcher() { - - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not Number - if (!item.isNumber()) return true; - if (!(item.asDouble() >= value)) { - mismatchDescription.appendText("value is not more than minimum " + value); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("minimum"); - } - }; - } - - public static Matcher isMultipleOf(final double value) { - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not Number - if (!item.isNumber()) return true; - - Number remainder = item.asDouble() % value; - if (!remainder.equals(0) && !remainder.equals(0.0)) { - mismatchDescription.appendText("value is not multipleOf " + value); - return false; - } - - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("multipleOf"); - } - }; - } - - // <== NUMBER <== - - - // ==> COMMON ==> - public static Matcher isOfType(final String type) { - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - if (type.equals(item.getJsonType())) - return true; - else { - mismatchDescription.appendText(", mismatch type '" + item.getJsonType() + "'"); - return false; - } - } - - @Override - public void describeTo(Description description) { - description.appendText("\nMatch to type: " + type); - } - }; - } - - public static Matcher isOfType(final List types) { - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - if (types.contains(item.getJsonType()) || (item.getJsonType().equals(TYPE_INTEGER) && types.contains(TYPE_NUMBER))) - return true; - else { - mismatchDescription.appendText(", mismatch type '" + item.getJsonType() + "'"); - return false; - } - } - - @Override - public void describeTo(Description description) { - description.appendText("\nMatch to one of types: " + types.toString()); - } - }; - } - - public static Matcher isInEnums(final JsonArray enums) { - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - - if (enums.contains(item)) { - return true; - } - - mismatchDescription.appendText(", mismatch value '" + item.toString() + "'"); - return false; - } - - @Override - public void describeTo(Description description) { - description.appendText("\nMatch to one of enum values: " + enums.toString()); - } - }; - } - - // <== COMMON <== - - // ==> ARRAY ==> - - public static Matcher areItemsValid(final Validator validator) { - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not JsonArray - if (!item.isJsonArray()) return true; - - for (int i = 0; i < item.asJsonArray().length(); i++) { - StringBuilder sb = new StringBuilder(); - if (!validator.validate(item.asJsonArray().opt(i), sb)) { - mismatchDescription.appendText("item at pos: " + i + ", does not validate by validator " + validator.getTitle()) - .appendText("\nDetails: ") - .appendText(sb.toString()); - return false; - } - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("are array items valid"); - } - }; - } - -// if(!validator.validate(item.asJsonObject().opt(property), sb)) { -// mismatchDescription.appendText(", mismatch value: " + item.asJsonObject().opt(property)) -// .appendText("\nDetails: ") -// .appendText(sb.toString()); -// return false; -// } - - public static Matcher isItemValid(final Validator validator, final int itemPos) { - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not JsonArray - if (!item.isJsonArray()) return true; - - //we also dont care if the item at position is not actually there - //if it is needed it will be handled by another matcher - if (item.asJsonArray().opt(itemPos) == null) return true; - StringBuilder sb = new StringBuilder(); - if (!validator.validate(item.asJsonArray().opt(itemPos), sb)) { - mismatchDescription.appendText("item at pos: " + itemPos + ", does not validate by validator " + validator.getTitle()) - .appendText("\nDetails: ") - .appendText(sb.toString()); - return false; - } - - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is array item valid"); - } - }; - } - - public static Matcher doesItemCountMatches(final int itemsCount) { - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not JsonArray - if (!item.isJsonArray()) return true; - - - if (item.asJsonArray().length() > itemsCount) { - mismatchDescription.appendText("items in Json array more than defined"); - return false; - } - - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("array items max count"); - } - }; - } - - public static Matcher maxItems(final int maxItems) { - return doesItemCountMatches(maxItems); - } - - public static Matcher minItems(final int minItems) { - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the items if parent item is not JsonArray - if (!item.isJsonArray()) return true; - - - if (item.asJsonArray().length() < minItems) { - mismatchDescription.appendText("items in Json array less than defined"); - return false; - } - - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("array items min count"); - } - }; - } - - - public static Matcher areItemsUnique() { - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the items if parent item is not JsonArray - if (!item.isJsonArray()) return true; - - JsonElement prevEl = null; - for (JsonElement el : item.asJsonArray()) { - if (prevEl != null && el.equals(prevEl)) { - mismatchDescription.appendText("items in Json array are not unique"); - return false; - } - prevEl = el; - } - - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("unique items"); - } - }; - } - - - // <== ARRAY <== - - // ==> OBJECT ==> - - - public static Matcher maxProperties(final int maxProperties) { - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not JsonObject - if (!item.isJsonObject()) return true; - - - if (item.asJsonObject().length() > maxProperties) { - mismatchDescription.appendText("properties in Json object more than defined"); - return false; - } - - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("object properties max count"); - } - }; - } - - public static Matcher minProperties(final int minProperties) { - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not JsonObject - if (!item.isJsonObject()) return true; - - - if (item.asJsonObject().length() < minProperties) { - mismatchDescription.appendText("properties in Json object less than defined"); - return false; - } - - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("object properties min count"); - } - }; - } - - - public static Matcher isPropertyPresent(final String property) { - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not JsonObject - if (!item.isJsonObject()) return true; - - if (!item.asJsonObject().has(property)) { - mismatchDescription.appendText(", does not exist in : " + item); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("\nCheck if property '" + property + "' exists"); - } - }; - } - - public static Matcher isPropertyValid(final Validator validator, final String property) { - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not JsonObject - if (!item.isJsonObject()) return true; - - //we also dont care if the property is not actually there - //if it is needed it will be handled by the "required" constraint on another matcher - if (!item.asJsonObject().has(property)) return true; - StringBuilder sb = new StringBuilder(); - if (!validator.validate(item.asJsonObject().opt(property), sb)) { - mismatchDescription.appendText(", mismatch value: " + item.asJsonObject().opt(property)) - .appendText("\nDetails: ") - .appendText(sb.toString()); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("\nMatch object property '" + property + "' with schema: " + ((SchemaValidator) validator).getSchema()); - } - }; - } - - public static Matcher isPropertyPatternValid(final Validator validator, final String propertyPattern) { - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not JsonObject - if (!item.isJsonObject()) return true; - - //we also dont care if the property is not actually there - //if it is needed it will be handled by the "required" constraint on another matcher - - Pattern p = Pattern.compile(propertyPattern); - for (Map.Entry entry : item.asJsonObject()) { - if (p.matcher(entry.getKey()).matches()) { - StringBuilder sb = new StringBuilder(); - if (!validator.validate(entry.getValue(), sb)) { - mismatchDescription.appendText(", mismatch of property: '" + entry.getKey() + "' with value: " + entry.getValue()) - .appendText("\nDetails: ") - .appendText(sb.toString()); - return false; - } - } - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("\nMatch object property pattern '" + propertyPattern + "' with schema: " + ((SchemaValidator) validator).getSchema()); - } - }; - } - - public static Matcher isNoAdditionalProperties(final Set properties, final Set patternProperties) { - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - //we do not care for the properties if parent item is not JsonObject - if (!item.isJsonObject()) return true; - - Set objectProps = new HashSet(); - objectProps.addAll(item.asJsonObject().keySet()); - - objectProps.removeAll(properties); - - - for (String pattern : patternProperties) { - Pattern p = Pattern.compile(pattern); - Iterator it = objectProps.iterator(); - while (it.hasNext()) { - String prop = it.next(); - if (p.matcher(prop).matches()) { - it.remove(); - } - } - } - - if (objectProps.size() > 0) { - for (String prop : objectProps) { - mismatchDescription.appendText("\nproperty: '" + prop + "' is not defined in the schema. "); - } - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("no additional properties exist except the ones defined in 'properties' and 'patternProperties' "); - } - }; - } - - - // <== OBJECT <== - - - // ==> GENERAL ==> - - /** - * General matcher - * - * @param validator - * @param element - * @return - */ - public static Matcher isElementValid(final Validator validator, final JsonElement element) { - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(JsonElement item, Description mismatchDescription) { - if (!validator.isValid(element)) { - mismatchDescription.appendText("element: " + element.toString() + ", does not validate by validator " + validator.getTitle()); - return false; - } - return true; - } - - @Override - public void describeTo(Description description) { - description.appendText("is array item valid"); - } - }; - } - - public static Matcher hasPattern(final String regex) { - final Pattern p = Pattern.compile(regex); - return new TypeSafeDiagnosingMatcher() { - @Override - protected boolean matchesSafely(CharSequence item, Description mismatchDescription) { - return p.matcher(item).matches(); - } - - @Override - public void describeTo(Description description) { - description.appendText("string matching pattern: " + p.pattern()); - } - }; - } - - - // <== GENERAL <== + public static Matcher areItemsUnique() { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("unique items"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the items if parent item is not JsonArray + if (!item.isJsonArray()) { + return true; + } + + JsonElement prevEl = null; + for (JsonElement el : item.asJsonArray()) { + if (prevEl != null && el.equals(prevEl)) { + mismatchDescription + .appendText("items in Json array are not unique"); + return false; + } + prevEl = el; + } + + return true; + } + }; + } + + public static Matcher areItemsValid(final Validator validator) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("are array items valid"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // JsonArray + if (!item.isJsonArray()) { + return true; + } + + for (int i = 0; i < item.asJsonArray().length(); i++) { + StringBuilder sb = new StringBuilder(); + if (!validator.validate(item.asJsonArray().opt(i), sb)) { + mismatchDescription + .appendText( + "item at pos: " + + i + + ", does not validate by validator " + + validator.getTitle()) + .appendText("\nDetails: ") + .appendText(sb.toString()); + return false; + } + } + return true; + } + }; + } + + public static Matcher doesItemCountMatches(final int itemsCount) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("array items max count"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // JsonArray + if (!item.isJsonArray()) { + return true; + } + + if (item.asJsonArray().length() > itemsCount) { + mismatchDescription + .appendText("items in Json array more than defined"); + return false; + } + + return true; + } + }; + } + + public static Matcher hasPattern(final String regex) { + final Pattern p = Pattern.compile(regex); + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("string matching pattern: " + + p.pattern()); + } + + @Override + protected boolean matchesSafely(final CharSequence item, + final Description mismatchDescription) { + return p.matcher(item).matches(); + } + }; + } + + // <== STRING <== + + // ==> NUMBER ==> + + /** + * General matcher + * + * @param validator + * @param element + * @return + */ + public static Matcher isElementValid( + final Validator validator, final JsonElement element) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("is array item valid"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + if (!validator.isValid(element)) { + mismatchDescription.appendText("element: " + + element.toString() + + ", does not validate by validator " + + validator.getTitle()); + return false; + } + return true; + } + }; + } + + public static Matcher isInEnums(final JsonArray enums) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("\nMatch to one of enum values: " + + enums.toString()); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + + if (enums.contains(item)) { + return true; + } + + mismatchDescription.appendText(", mismatch value '" + + item.toString() + "'"); + return false; + } + }; + } + + public static Matcher isItemValid(final Validator validator, + final int itemPos) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("is array item valid"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // JsonArray + if (!item.isJsonArray()) { + return true; + } + + // we also dont care if the item at position is not actually + // there + // if it is needed it will be handled by another matcher + if (item.asJsonArray().opt(itemPos) == null) { + return true; + } + StringBuilder sb = new StringBuilder(); + if (!validator.validate(item.asJsonArray().opt(itemPos), sb)) { + mismatchDescription + .appendText( + "item at pos: " + + itemPos + + ", does not validate by validator " + + validator.getTitle()) + .appendText("\nDetails: ") + .appendText(sb.toString()); + return false; + } + + return true; + } + }; + } + + public static Matcher isLessOrEqualThan(final double value) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("maximum"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // Number + if (!item.isNumber()) { + return true; + } + if (!(item.asDouble() <= value)) { + mismatchDescription + .appendText("value is not less than maximum " + + value); + return false; + } + return true; + } + }; + } + + public static Matcher isLessThan(final double value) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("exclusive maximum"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // Number + if (!item.isNumber()) { + return true; + } + if (!(item.asDouble() < value)) { + mismatchDescription + .appendText("value is not less than exclusive maximum " + + value); + return false; + } + return true; + } + }; + } + + // <== NUMBER <== + + public static Matcher isMoreOrEqualThan(final double value) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("minimum"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // Number + if (!item.isNumber()) { + return true; + } + if (!(item.asDouble() >= value)) { + mismatchDescription + .appendText("value is not more than minimum " + + value); + return false; + } + return true; + } + }; + } + + public static Matcher isMoreThan(final double value) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("exclusive minimum"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // Number + if (!item.isNumber()) { + return true; + } + if (!(item.asDouble() > value)) { + mismatchDescription + .appendText("value is not more than exclusive minimum " + + value); + return false; + } + return true; + } + }; + } + + public static Matcher isMultipleOf(final double value) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("multipleOf"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // Number + if (!item.isNumber()) { + return true; + } + + Number remainder = item.asDouble() % value; + if (!remainder.equals(0) && !remainder.equals(0.0)) { + mismatchDescription.appendText("value is not multipleOf " + + value); + return false; + } + + return true; + } + }; + } + + // <== COMMON <== + + // ==> ARRAY ==> + + public static Matcher isNoAdditionalProperties( + final Set properties, final Set patternProperties) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description + .appendText("no additional properties exist except the ones defined in 'properties' and 'patternProperties' "); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // JsonObject + if (!item.isJsonObject()) { + return true; + } + + Set objectProps = new HashSet(); + objectProps.addAll(item.asJsonObject().keySet()); + + objectProps.removeAll(properties); + + for (String pattern : patternProperties) { + Pattern p = Pattern.compile(pattern); + Iterator it = objectProps.iterator(); + while (it.hasNext()) { + String prop = it.next(); + if (p.matcher(prop).matches()) { + it.remove(); + } + } + } + + if (objectProps.size() > 0) { + for (String prop : objectProps) { + mismatchDescription.appendText("\nproperty: '" + prop + + "' is not defined in the schema. "); + } + return false; + } + return true; + } + }; + } + + // if(!validator.validate(item.asJsonObject().opt(property), sb)) { + // mismatchDescription.appendText(", mismatch value: " + + // item.asJsonObject().opt(property)) + // .appendText("\nDetails: ") + // .appendText(sb.toString()); + // return false; + // } + + public static Matcher isOfType(final List types) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("\nMatch to one of types: " + + types.toString()); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + if (types.contains(item.getJsonType()) + || (item.getJsonType().equals(TYPE_INTEGER) && types + .contains(TYPE_NUMBER))) { + return true; + } else { + mismatchDescription.appendText(", mismatch type '" + + item.getJsonType() + "'"); + return false; + } + } + }; + } + + // ==> COMMON ==> + public static Matcher isOfType(final String type) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("\nMatch to type: " + type); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + if (type.equals(item.getJsonType())) { + return true; + } else { + mismatchDescription.appendText(", mismatch type '" + + item.getJsonType() + "'"); + return false; + } + } + }; + } + + public static Matcher isPropertyPatternValid( + final Validator validator, final String propertyPattern) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("\nMatch object property pattern '" + + propertyPattern + "' with schema: " + + ((SchemaValidator) validator).getSchema()); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // JsonObject + if (!item.isJsonObject()) { + return true; + } + + // we also dont care if the property is not actually there + // if it is needed it will be handled by the "required" + // constraint on another matcher + + Pattern p = Pattern.compile(propertyPattern); + for (Map.Entry entry : item.asJsonObject()) { + if (p.matcher(entry.getKey()).matches()) { + StringBuilder sb = new StringBuilder(); + if (!validator.validate(entry.getValue(), sb)) { + mismatchDescription + .appendText( + ", mismatch of property: '" + + entry.getKey() + + "' with value: " + + entry.getValue()) + .appendText("\nDetails: ") + .appendText(sb.toString()); + return false; + } + } + } + return true; + } + }; + } + + public static Matcher isPropertyPresent(final String property) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("\nCheck if property '" + property + + "' exists"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // JsonObject + if (!item.isJsonObject()) { + return true; + } + + if (!item.asJsonObject().has(property)) { + mismatchDescription.appendText(", does not exist in : " + + item); + return false; + } + return true; + } + }; + } + + public static Matcher isPropertyValid( + final Validator validator, final String property) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("\nMatch object property '" + property + + "' with schema: " + + ((SchemaValidator) validator).getSchema()); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // JsonObject + if (!item.isJsonObject()) { + return true; + } + + // we also dont care if the property is not actually there + // if it is needed it will be handled by the "required" + // constraint on another matcher + if (!item.asJsonObject().has(property)) { + return true; + } + StringBuilder sb = new StringBuilder(); + if (!validator.validate(item.asJsonObject().opt(property), sb)) { + mismatchDescription + .appendText( + ", mismatch value: " + + item.asJsonObject().opt(property)) + .appendText("\nDetails: ") + .appendText(sb.toString()); + return false; + } + return true; + } + }; + } + + // <== ARRAY <== + + // ==> OBJECT ==> + + public static Matcher matchesPattern(final String value) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("Pattern match"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // String + if (!item.isString()) { + return true; + } + if (!Pattern.matches(value, item.asString())) { + mismatchDescription.appendText("Pattern '" + value + + "' does not match '" + item.asString() + "'"); + return false; + } + return true; + } + }; + } + + public static Matcher maxItems(final int maxItems) { + return doesItemCountMatches(maxItems); + } + + public static Matcher maxProperties(final int maxProperties) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("object properties max count"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // JsonObject + if (!item.isJsonObject()) { + return true; + } + + if (item.asJsonObject().length() > maxProperties) { + mismatchDescription + .appendText("properties in Json object more than defined"); + return false; + } + + return true; + } + }; + } + + public static Matcher minItems(final int minItems) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("array items min count"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the items if parent item is not JsonArray + if (!item.isJsonArray()) { + return true; + } + + if (item.asJsonArray().length() < minItems) { + mismatchDescription + .appendText("items in Json array less than defined"); + return false; + } + + return true; + } + }; + } + + public static Matcher minProperties(final int minProperties) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("object properties min count"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // JsonObject + if (!item.isJsonObject()) { + return true; + } + + if (item.asJsonObject().length() < minProperties) { + mismatchDescription + .appendText("properties in Json object less than defined"); + return false; + } + + return true; + } + }; + } + + // ==> STRING ==> + public static Matcher withCharsLessOrEqualTo(final int value) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("String maximum length"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // String + if (!item.isString()) { + return true; + } + if (item.asString().length() > value) { + mismatchDescription + .appendText("String length more than maximum value: " + + value); + return false; + } + return true; + } + }; + } + + // <== OBJECT <== + + // ==> GENERAL ==> + + public static Matcher withCharsMoreOrEqualTo(final int value) { + return new TypeSafeDiagnosingMatcher() { + + public void describeTo(final Description description) { + description.appendText("String minimum length"); + } + + @Override + protected boolean matchesSafely(final JsonElement item, + final Description mismatchDescription) { + // we do not care for the properties if parent item is not + // String + if (!item.isString()) { + return true; + } + if (item.asString().length() < value) { + mismatchDescription + .appendText("String length less than minimum value: " + + value); + return false; + } + return true; + } + }; + } + + private CommonMatchers() { + } + + // <== GENERAL <== } diff --git a/json-schema/src/main/java/io/apptik/json/schema/validation/SchemaV4Validator.java b/json-schema/src/main/java/io/apptik/json/schema/validation/SchemaV4Validator.java index 195aba2..b4012a8 100644 --- a/json-schema/src/main/java/io/apptik/json/schema/validation/SchemaV4Validator.java +++ b/json-schema/src/main/java/io/apptik/json/schema/validation/SchemaV4Validator.java @@ -16,210 +16,246 @@ package io.apptik.json.schema.validation; - +import static io.apptik.json.schema.validation.CommonMatchers.areItemsUnique; +import static io.apptik.json.schema.validation.CommonMatchers.areItemsValid; +import static io.apptik.json.schema.validation.CommonMatchers.doesItemCountMatches; +import static io.apptik.json.schema.validation.CommonMatchers.isInEnums; +import static io.apptik.json.schema.validation.CommonMatchers.isItemValid; +import static io.apptik.json.schema.validation.CommonMatchers.isLessOrEqualThan; +import static io.apptik.json.schema.validation.CommonMatchers.isLessThan; +import static io.apptik.json.schema.validation.CommonMatchers.isMoreOrEqualThan; +import static io.apptik.json.schema.validation.CommonMatchers.isMoreThan; +import static io.apptik.json.schema.validation.CommonMatchers.isMultipleOf; +import static io.apptik.json.schema.validation.CommonMatchers.isNoAdditionalProperties; +import static io.apptik.json.schema.validation.CommonMatchers.isOfType; +import static io.apptik.json.schema.validation.CommonMatchers.isPropertyPatternValid; +import static io.apptik.json.schema.validation.CommonMatchers.isPropertyPresent; +import static io.apptik.json.schema.validation.CommonMatchers.isPropertyValid; +import static io.apptik.json.schema.validation.CommonMatchers.matchesPattern; +import static io.apptik.json.schema.validation.CommonMatchers.maxItems; +import static io.apptik.json.schema.validation.CommonMatchers.maxProperties; +import static io.apptik.json.schema.validation.CommonMatchers.minItems; +import static io.apptik.json.schema.validation.CommonMatchers.minProperties; +import static io.apptik.json.schema.validation.CommonMatchers.withCharsLessOrEqualTo; +import static io.apptik.json.schema.validation.CommonMatchers.withCharsMoreOrEqualTo; +import static org.hamcrest.Matchers.allOf; import io.apptik.json.JsonArray; import io.apptik.json.JsonElement; import io.apptik.json.schema.Schema; import io.apptik.json.schema.SchemaList; import io.apptik.json.schema.SchemaMap; import io.apptik.json.schema.SchemaV4; -import org.hamcrest.Matcher; -import org.hamcrest.StringDescription; -import java.util.*; - -import static io.apptik.json.schema.validation.CommonMatchers.*; -import static org.hamcrest.Matchers.allOf; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.hamcrest.Matcher; +import org.hamcrest.StringDescription; public class SchemaV4Validator extends SchemaValidator { - ArrayList> allMatchers = new ArrayList>(); - - public SchemaV4Validator(SchemaV4 schema) { - super(schema); - //allMatchers - putMatchers4Numeric(); - putMatchers4String(); - putMatchers4Array(); - putMatcher4Object(); - putMatchers4Common(); - - } - - - private void putMatchers4Numeric() { - Double multipleOf = schema.getMultipleOf(); - if(multipleOf != null && multipleOf>0) { - allMatchers.add(isMultipleOf(multipleOf)); - } - - Double maximum = schema.getMaximum(); - if(maximum!=null) { - if(schema.getExclusiveMaximum()) { - allMatchers.add(isLessThan(maximum)); - } else { - allMatchers.add(isLessOrEqualThan(maximum)); - } - } - - Double minimum = schema.getMinimum(); - if(minimum!=null) { - if(schema.getExclusiveMinimum()) { - allMatchers.add(isMoreThan(minimum)); - } else { - allMatchers.add(isMoreOrEqualThan(minimum)); - } - } - - } - - private void putMatchers4String() { - - Integer maxLength = schema.getMaxLength(); - if(maxLength != null) { - allMatchers.add(withCharsLessOrEqualTo(maxLength)); - } - - Integer minLength = schema.getMinLength(); - if(minLength!=null && minLength > 0) { - allMatchers.add(withCharsMoreOrEqualTo(minLength)); - } - - String pattern = schema.getPattern(); - if(pattern != null && !pattern.isEmpty()) { - allMatchers.add(matchesPattern(pattern)); - } - - - - } - private void putMatchers4Array() { - - SchemaList items = schema.getItems(); - if(items != null && !items.isEmpty()) { - if(items.size() == 1) { - //single type for array items - allMatchers.add(areItemsValid(items.get(0).getDefaultValidator())); - } else { - //tuple typing - //first check if schemas needs to be the same number as the instance items - if(!schema.getAdditionalItems()) { - allMatchers.add(doesItemCountMatches(items.size())); - } - - //then check if available items are according to provided schemas - for(int i=0;i 0) { - allMatchers.add(minProperties(minProperties)); - } - - List required = schema.getRequired(); - if(required != null && !required.isEmpty()) { - for(String param : required) { - allMatchers.add(isPropertyPresent(param)); - } - } - - //validates only child properties if any found matching the property names - SchemaMap propertiesSchemaMap = schema.getProperties(); - if(propertiesSchemaMap != null && propertiesSchemaMap.length() > 0) { - for(Map.Entry entry : propertiesSchemaMap) { - allMatchers.add(isPropertyValid(entry.getValue().getDefaultValidator(), entry.getKey())); - } - } - - //validates only child properties if any found matching the property patterns - SchemaMap patternPropertiesSchemaMap = schema.getPatternProperties(); - if(patternPropertiesSchemaMap != null && patternPropertiesSchemaMap.length() > 0) { - for(Map.Entry entry : patternPropertiesSchemaMap) { - allMatchers.add(isPropertyPatternValid(entry.getValue().getDefaultValidator(), entry.getKey())); - } - } - - boolean additionalProperties = schema.getAdditionalProperties(); - if(!additionalProperties) { - Set proppertiesSet; - Set patternPropertiesSet; - if(propertiesSchemaMap == null || propertiesSchemaMap.getEntries() == null) { - proppertiesSet = Collections.EMPTY_SET; - } else { - proppertiesSet = propertiesSchemaMap.getEntries().keySet(); - } - - if(patternPropertiesSchemaMap ==null || patternPropertiesSchemaMap.getEntries() ==null) { - patternPropertiesSet = Collections.EMPTY_SET; - } else { - patternPropertiesSet = patternPropertiesSchemaMap.getEntries().keySet(); - } - allMatchers.add(isNoAdditionalProperties(proppertiesSet, patternPropertiesSet)); - } - - //TODO as per : http://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.4.5 - - } - - private void putMatchers4Common() { - - List schemaType = schema.getType(); - if(schemaType != null && !schemaType.isEmpty()) { - allMatchers.add(isOfType(schemaType)); - } - - JsonArray enums = schema.getEnum(); - if(enums != null) { - allMatchers.add(isInEnums(enums)); - } - - //TODO anyOf,oneOf,allOf,no + optional format - - } - - @Override - protected boolean doValidate(JsonElement el, StringBuilder sb) { - //System.out.println("SchemaV4Validator start: " + this.getTitle()); - //check if empty schema - if(allMatchers == null || allMatchers.isEmpty()) { - System.out.println("SchemaV4Validator NO MATCHERS end: " + this.getTitle()); - return true; - } - - Matcher matcher = allOf(allMatchers); - //System.out.println("SchemaV4Validator end: " + this.getTitle()); - if(sb!=null) { - matcher.describeMismatch(el, new StringDescription(sb)); - } - return matcher.matches(el); - } + ArrayList> allMatchers = new ArrayList>(); + + public SchemaV4Validator(final SchemaV4 schema) { + super(schema); + // allMatchers + putMatchers4Numeric(); + putMatchers4String(); + putMatchers4Array(); + putMatcher4Object(); + putMatchers4Common(); + + } + + @Override + protected boolean doValidate(final JsonElement el, final StringBuilder sb) { + // System.out.println("SchemaV4Validator start: " + this.getTitle()); + // check if empty schema + if (allMatchers == null || allMatchers.isEmpty()) { + System.out.println("SchemaV4Validator NO MATCHERS end: " + + this.getTitle()); + return true; + } + + Matcher matcher = allOf(allMatchers); + // System.out.println("SchemaV4Validator end: " + this.getTitle()); + if (sb != null) { + matcher.describeMismatch(el, new StringDescription(sb)); + } + return matcher.matches(el); + + } + + private void putMatcher4Object() { + + Integer maxProperties = schema.getMaxProperties(); + if (maxProperties != null) { + allMatchers.add(maxProperties(maxProperties)); + } + + Integer minProperties = schema.getMinProperties(); + if (minProperties != null && minProperties > 0) { + allMatchers.add(minProperties(minProperties)); + } + + List required = schema.getRequired(); + if (required != null && !required.isEmpty()) { + for (String param : required) { + allMatchers.add(isPropertyPresent(param)); + } + } + + // validates only child properties if any found matching the property + // names + SchemaMap propertiesSchemaMap = schema.getProperties(); + if (propertiesSchemaMap != null && propertiesSchemaMap.length() > 0) { + for (Map.Entry entry : propertiesSchemaMap) { + allMatchers.add(isPropertyValid(entry.getValue() + .getDefaultValidator(), entry.getKey())); + } + } + + // validates only child properties if any found matching the property + // patterns + SchemaMap patternPropertiesSchemaMap = schema.getPatternProperties(); + if (patternPropertiesSchemaMap != null + && patternPropertiesSchemaMap.length() > 0) { + for (Map.Entry entry : patternPropertiesSchemaMap) { + allMatchers.add(isPropertyPatternValid(entry.getValue() + .getDefaultValidator(), entry.getKey())); + } + } + + boolean additionalProperties = schema.getAdditionalProperties(); + if (!additionalProperties) { + Set proppertiesSet; + Set patternPropertiesSet; + if (propertiesSchemaMap == null + || propertiesSchemaMap.getEntries() == null) { + proppertiesSet = Collections.EMPTY_SET; + } else { + proppertiesSet = propertiesSchemaMap.getEntries().keySet(); + } + + if (patternPropertiesSchemaMap == null + || patternPropertiesSchemaMap.getEntries() == null) { + patternPropertiesSet = Collections.EMPTY_SET; + } else { + patternPropertiesSet = patternPropertiesSchemaMap.getEntries() + .keySet(); + } + allMatchers.add(isNoAdditionalProperties(proppertiesSet, + patternPropertiesSet)); + } + + // TODO as per : + // http://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.4.5 + + } + + private void putMatchers4Array() { + + SchemaList items = schema.getItems(); + if (items != null && !items.isEmpty()) { + if (items.size() == 1) { + // single type for array items + allMatchers.add(areItemsValid(items.get(0) + .getDefaultValidator())); + } else { + // tuple typing + // first check if schemas needs to be the same number as the + // instance items + if (!schema.getAdditionalItems()) { + allMatchers.add(doesItemCountMatches(items.size())); + } + + // then check if available items are according to provided + // schemas + for (int i = 0; i < items.size(); i++) { + allMatchers.add(isItemValid(items.get(i) + .getDefaultValidator(), i)); + } + + } + } + + Integer maxItems = schema.getMaxItems(); + if (maxItems != null) { + allMatchers.add(maxItems(maxItems)); + } + + Integer minItems = schema.getMinItems(); + if (minItems != null) { + allMatchers.add(minItems(minItems)); + } + + if (schema.getUniqueItems()) { + allMatchers.add(areItemsUnique()); + } + + } + + private void putMatchers4Common() { + + List schemaType = schema.getType(); + if (schemaType != null && !schemaType.isEmpty()) { + allMatchers.add(isOfType(schemaType)); + } + + JsonArray enums = schema.getEnum(); + if (enums != null) { + allMatchers.add(isInEnums(enums)); + } + + // TODO anyOf,oneOf,allOf,no + optional format + + } + + private void putMatchers4Numeric() { + Double multipleOf = schema.getMultipleOf(); + if (multipleOf != null && multipleOf > 0) { + allMatchers.add(isMultipleOf(multipleOf)); + } + + Double maximum = schema.getMaximum(); + if (maximum != null) { + if (schema.getExclusiveMaximum()) { + allMatchers.add(isLessThan(maximum)); + } else { + allMatchers.add(isLessOrEqualThan(maximum)); + } + } + + Double minimum = schema.getMinimum(); + if (minimum != null) { + if (schema.getExclusiveMinimum()) { + allMatchers.add(isMoreThan(minimum)); + } else { + allMatchers.add(isMoreOrEqualThan(minimum)); + } + } + + } + + private void putMatchers4String() { + + Integer maxLength = schema.getMaxLength(); + if (maxLength != null) { + allMatchers.add(withCharsLessOrEqualTo(maxLength)); + } + + Integer minLength = schema.getMinLength(); + if (minLength != null && minLength > 0) { + allMatchers.add(withCharsMoreOrEqualTo(minLength)); + } + + String pattern = schema.getPattern(); + if (pattern != null && !pattern.isEmpty()) { + allMatchers.add(matchesPattern(pattern)); + } + + } } diff --git a/json-schema/src/test/java/io/apptik/json/schema/JsonSchemaV4Test.java b/json-schema/src/test/java/io/apptik/json/schema/JsonSchemaV4Test.java index 4160537..c52cde5 100644 --- a/json-schema/src/test/java/io/apptik/json/schema/JsonSchemaV4Test.java +++ b/json-schema/src/test/java/io/apptik/json/schema/JsonSchemaV4Test.java @@ -16,35 +16,36 @@ package io.apptik.json.schema; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.net.URI; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.net.URI; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - @RunWith(JUnit4.class) -public class JsonSchemaV4Test{ - Schema schema; - - @Before - public void setUp() throws Exception { - schema = new SchemaV4(); - } - - @Test - public void testSchemaUri() throws Exception { - assertEquals(schema.getSchema(),"http://json-schema.org/draft-04/schema#"); - assertEquals(schema.getJsonSchemaUri(), URI.create("http://json-schema.org/draft-04/schema#")); - } - - @Test - public void testValidator() throws Exception { - assertNotNull(schema.getDefaultValidator()); - - } +public class JsonSchemaV4Test { + Schema schema; + + @Before + public void setUp() throws Exception { + schema = new SchemaV4(); + } + + @Test + public void testSchemaUri() throws Exception { + assertEquals(schema.getSchema(), + "http://json-schema.org/draft-04/schema#"); + assertEquals(schema.getJsonSchemaUri(), + URI.create("http://json-schema.org/draft-04/schema#")); + } + + @Test + public void testValidator() throws Exception { + assertNotNull(schema.getDefaultValidator()); + + } } diff --git a/json-wrapper/.gitignore b/json-wrapper/.gitignore index 796b96d..c4dfdc5 100644 --- a/json-wrapper/.gitignore +++ b/json-wrapper/.gitignore @@ -1 +1,2 @@ /build +/target/ diff --git a/json-wrapper/src/main/java/io/apptik/json/wrapper/CachedTypedJsonArray.java b/json-wrapper/src/main/java/io/apptik/json/wrapper/CachedTypedJsonArray.java index c946cec..14eef39 100644 --- a/json-wrapper/src/main/java/io/apptik/json/wrapper/CachedTypedJsonArray.java +++ b/json-wrapper/src/main/java/io/apptik/json/wrapper/CachedTypedJsonArray.java @@ -1,203 +1,206 @@ package io.apptik.json.wrapper; - import io.apptik.json.JsonArray; import io.apptik.json.JsonElement; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; /** - * This class is used for wrapping json arrays where elements - * are of the same type. It is using ArrayList to save all translated elements - * and performs async mapping when wrapped. Until wrapping is done only get method - * is available for parsed elements. - * This is useful when a big array of complex elements needs to be loaded from json - * and only displayed multiple times. + * This class is used for wrapping json arrays where elements are of the same + * type. It is using ArrayList to save all translated elements and performs + * async mapping when wrapped. Until wrapping is done only get method is + * available for parsed elements. This is useful when a big array of complex + * elements needs to be loaded from json and only displayed multiple times. */ public abstract class CachedTypedJsonArray extends TypedJsonArray { - //we need reference-equality in place of object-equality when comparing original json elements - List elements = Collections.synchronizedList(new ArrayList()); - volatile boolean wrapping; - - @Override - public T wrap(JsonArray jsonElement) { - super.wrap(jsonElement); - wrapElements(); - return (T) this; - } - - private synchronized void wrapElements() { - wrapping = true; - elements.clear(); - Thread t = new Thread(new Runnable() { - @Override - public void run() { - for (JsonElement je : getJson()) { - elements.add(get(je, elements.size())); - } - wrapping = false; - } - }); - t.setPriority(Thread.MIN_PRIORITY); - t.start(); - } - - @Override - public T get(int i) { - if (i >= elements.size()) { - return get(super.getJson().get(i), i); - } else { - return elements.get(i); - } - } - - @Override - public T set(int i, T t) { - blockUntilWrapped(); - return elements.set(i, t); - } - - @Override - public void add(int i, T t) { - blockUntilWrapped(); - elements.add(i, t); - } - - @Override - public T remove(int i) { - blockUntilWrapped(); - return elements.remove(i); - } - - @Override - public int indexOf(Object o) { - blockUntilWrapped(); - return elements.indexOf(o); - } - - @Override - public int lastIndexOf(Object o) { - blockUntilWrapped(); - return elements.lastIndexOf(o); - } - - @Override - public ListIterator listIterator() { - blockUntilWrapped(); - return elements.listIterator(); - } - - @Override - public ListIterator listIterator(int i) { - blockUntilWrapped(); - return elements.listIterator(i); - } - - @Override - public List subList(int i, int i2) { - blockUntilWrapped(); - return elements.subList(i, i2); - } - - @Override - public void clear() { - blockUntilWrapped(); - elements.clear(); - } - - @Override - public boolean retainAll(Collection objects) { - blockUntilWrapped(); - return elements.retainAll(objects); - } - - @Override - public boolean removeAll(Collection objects) { - blockUntilWrapped(); - return elements.removeAll(objects); - } - - @Override - public boolean addAll(int i, Collection ts) { - blockUntilWrapped(); - return elements.addAll(i, ts); - } - - @Override - public boolean addAll(Collection ts) { - blockUntilWrapped(); - return elements.addAll(ts); - } - - @Override - public boolean containsAll(Collection objects) { - blockUntilWrapped(); - return elements.containsAll(objects); - } - - @Override - public boolean remove(Object o) { - blockUntilWrapped(); - return elements.remove(o); - } - - @Override - public boolean add(T t) { - blockUntilWrapped(); - return elements.add(t); - } - - @Override - public Iterator iterator() { - blockUntilWrapped(); - return elements.iterator(); - } - - @Override - public boolean contains(Object o) { - blockUntilWrapped(); - return elements.contains(o); - } - - @Override - public int size() { - if (wrapping) { - return json.asJsonArray().size(); - } else { - return elements.size(); - } - } - - @Override - public Object[] toArray() { - blockUntilWrapped(); - return elements.toArray(); - } - - @Override - public T1[] toArray(T1[] t1s) { - blockUntilWrapped(); - return elements.toArray(t1s); - } - - - @Override - public JsonArray getJson() { - this.json.asJsonArray().clear(); - for (T el : elements) { - this.json.asJsonArray().put(to(el)); - } - return super.getJson(); - } - - private void blockUntilWrapped() { - while (wrapping) { - try { - Thread.sleep(33); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } + // we need reference-equality in place of object-equality when comparing + // original json elements + List elements = Collections.synchronizedList(new ArrayList()); + volatile boolean wrapping; + + @Override + public void add(final int i, final T t) { + blockUntilWrapped(); + elements.add(i, t); + } + + @Override + public boolean add(final T t) { + blockUntilWrapped(); + return elements.add(t); + } + + @Override + public boolean addAll(final Collection ts) { + blockUntilWrapped(); + return elements.addAll(ts); + } + + @Override + public boolean addAll(final int i, final Collection ts) { + blockUntilWrapped(); + return elements.addAll(i, ts); + } + + private void blockUntilWrapped() { + while (wrapping) { + try { + Thread.sleep(33); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + @Override + public void clear() { + blockUntilWrapped(); + elements.clear(); + } + + @Override + public boolean contains(final Object o) { + blockUntilWrapped(); + return elements.contains(o); + } + + @Override + public boolean containsAll(final Collection objects) { + blockUntilWrapped(); + return elements.containsAll(objects); + } + + @Override + public T get(final int i) { + if (i >= elements.size()) { + return get(super.getJson().get(i), i); + } else { + return elements.get(i); + } + } + + @Override + public JsonArray getJson() { + this.json.asJsonArray().clear(); + for (T el : elements) { + this.json.asJsonArray().put(to(el)); + } + return super.getJson(); + } + + @Override + public int indexOf(final Object o) { + blockUntilWrapped(); + return elements.indexOf(o); + } + + @Override + public Iterator iterator() { + blockUntilWrapped(); + return elements.iterator(); + } + + @Override + public int lastIndexOf(final Object o) { + blockUntilWrapped(); + return elements.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + blockUntilWrapped(); + return elements.listIterator(); + } + + @Override + public ListIterator listIterator(final int i) { + blockUntilWrapped(); + return elements.listIterator(i); + } + + @Override + public T remove(final int i) { + blockUntilWrapped(); + return elements.remove(i); + } + + @Override + public boolean remove(final Object o) { + blockUntilWrapped(); + return elements.remove(o); + } + + @Override + public boolean removeAll(final Collection objects) { + blockUntilWrapped(); + return elements.removeAll(objects); + } + + @Override + public boolean retainAll(final Collection objects) { + blockUntilWrapped(); + return elements.retainAll(objects); + } + + @Override + public T set(final int i, final T t) { + blockUntilWrapped(); + return elements.set(i, t); + } + + @Override + public int size() { + if (wrapping) { + return json.asJsonArray().size(); + } else { + return elements.size(); + } + } + + @Override + public List subList(final int i, final int i2) { + blockUntilWrapped(); + return elements.subList(i, i2); + } + + @Override + public Object[] toArray() { + blockUntilWrapped(); + return elements.toArray(); + } + + @Override + public T1[] toArray(final T1[] t1s) { + blockUntilWrapped(); + return elements.toArray(t1s); + } + + @Override + public T wrap(final JsonArray jsonElement) { + super.wrap(jsonElement); + wrapElements(); + return (T) this; + } + + private synchronized void wrapElements() { + wrapping = true; + elements.clear(); + Thread t = new Thread(new Runnable() { + + public void run() { + for (JsonElement je : getJson()) { + elements.add(get(je, elements.size())); + } + wrapping = false; + } + }); + t.setPriority(Thread.MIN_PRIORITY); + t.start(); + } } diff --git a/json-wrapper/src/main/java/io/apptik/json/wrapper/JsonElementWrapper.java b/json-wrapper/src/main/java/io/apptik/json/wrapper/JsonElementWrapper.java index 46718b0..5439b83 100644 --- a/json-wrapper/src/main/java/io/apptik/json/wrapper/JsonElementWrapper.java +++ b/json-wrapper/src/main/java/io/apptik/json/wrapper/JsonElementWrapper.java @@ -14,9 +14,13 @@ * limitations under the License. */ - package io.apptik.json.wrapper; +import io.apptik.json.ElementWrapper; +import io.apptik.json.JsonElement; +import io.apptik.json.Validator; +import io.apptik.json.exception.JsonException; +import io.apptik.json.util.LinkedTreeMap; import java.io.IOException; import java.io.ObjectInputStream; @@ -26,214 +30,224 @@ import java.util.LinkedHashSet; import java.util.Map; -import io.apptik.json.ElementWrapper; -import io.apptik.json.JsonElement; -import io.apptik.json.JsonObject; -import io.apptik.json.Validator; -import io.apptik.json.exception.JsonException; -import io.apptik.json.util.LinkedTreeMap; - - /** - * Json element Wrapper to be used to wrap JSON data with its schema content type used to describe the data. + * Json element Wrapper to be used to wrap JSON data with its schema content + * type used to describe the data. *

- * It can be extended to define a data model implementing getters and setters for required members. + * It can be extended to define a data model implementing getters and setters + * for required members. *

- * The idea is that there is no need to instantiate different POJO for different purpose on the same JSON data, - * but just wrap that data. + * The idea is that there is no need to instantiate different POJO for different + * purpose on the same JSON data, but just wrap that data. *

- * Compared to pure POJO mapping it is particularly useful when single JSON representation can be mapped to 2 or more - * POJOs. For example if we get data about https://schema.org/LocalBusiness which is https://schema.org/Organization - * and https://schema.org/Place at the same time we need to reference Place and Organization POJOs in the LocalBusiness - * POJO as in java we cannot extend from 2 classes. This means that for any possible combination of DataTypes available - * for the Resources we get we need to create POJOs to delegate to others. + * Compared to pure POJO mapping it is particularly useful when single JSON + * representation can be mapped to 2 or more POJOs. For example if we get data + * about https://schema.org/LocalBusiness which is + * https://schema.org/Organization and https://schema.org/Place at the same time + * we need to reference Place and Organization POJOs in the LocalBusiness POJO + * as in java we cannot extend from 2 classes. This means that for any possible + * combination of DataTypes available for the Resources we get we need to create + * POJOs to delegate to others. *

- * Using Json wrappers is fairly simple as we can just create wrappers implementing interfaces specific to the type we - * need to work with, while full json data itself is still available to be wrapped again for other needs. + * Using Json wrappers is fairly simple as we can just create wrappers + * implementing interfaces specific to the type we need to work with, while full + * json data itself is still available to be wrapped again for other needs. */ -public abstract class JsonElementWrapper implements ElementWrapper { - - protected transient T json; - private String contentType; - private URI jsonSchemaUri; - private MetaInfo metaInfo; - - private transient LinkedHashSet validators; - private transient LinkedTreeMap fetchers = new LinkedTreeMap(); - - @Override - public T getJson() { - return json; - } - - public String getContentType() { - return contentType; - } - - public URI getJsonSchemaUri() { - return jsonSchemaUri; - } - - public MetaInfo getMetaInfo() { - return metaInfo; - } - - public JsonElementWrapper() { - //todo do we need default fetcher ? - // fetchers.put("defaultUriFetcher", new SchemaUriFetcher()); - } - - public JsonElementWrapper(T jsonElement) { - this.json = jsonElement; - } - - public JsonElementWrapper(T jsonElement, String contentType) { - this(jsonElement); - this.contentType = contentType; - - } - - public JsonElementWrapper(T jsonElement, String contentType, URI metaInfo) { - this(jsonElement, contentType); - this.jsonSchemaUri = metaInfo; - tryFetchMetaInfo(this.jsonSchemaUri); - } - - - /** - * Tries to fetch a schema and add the default Schema validator for it - * - * @param jsonSchemaUri - * @return - */ - private MetaInfo tryFetchMetaInfo(URI jsonSchemaUri) { - if (jsonSchemaUri == null) return null; - try { - metaInfo = doFetchMetaInfo(jsonSchemaUri); - Validator validator = metaInfo.getDefaultValidator(); - if (validator != null) { - getValidators().add(validator); - } - } catch (Exception ex) { - return null; - } - - return metaInfo; - } - - private MetaInfo doFetchMetaInfo(URI jsonSchemaUri) { - MetaInfo currSchema = null; - - Iterator> iterator = fetchers.entrySet().iterator(); - while (iterator.hasNext() && currSchema == null) { - Map.Entry entry = iterator.next(); - System.out.println("JsonElementWrapper try fetch using: " + entry.getKey()); - try { - currSchema = entry.getValue().fetch(jsonSchemaUri); - } catch (IOException e) { - e.printStackTrace(); - } - System.out.println("JsonElementWrapper fetch result: " + ((currSchema == null) ? "FAIL" : "OK")); - } - return currSchema; - } - - public J wrap(T jsonElement) { - this.json = jsonElement; - return (J) this; - } - - public JsonElementWrapper setContentType(String contentType) { - this.contentType = contentType; - return this; - } - - public JsonElementWrapper setMetaInfoUri(URI uri) { - this.jsonSchemaUri = uri; - tryFetchMetaInfo(this.jsonSchemaUri); - return this; - } - - public JsonElementWrapper setMetaInfo(MetaInfo metaInfo) { - this.metaInfo = metaInfo; - return this; - } - - public JsonElementWrapper addSchemaFetcher(String name, MetaInfoFetcher fetcher) { - this.fetchers.put(name, fetcher); - return this; - } - - public MetaInfoFetcher getDefaultSchemaFetcher() { - return this.fetchers.get("defaultUriFetcher"); - } - - public JsonElementWrapper setDefaultSchemaFetcher(MetaInfoFetcher fetcher) { - this.fetchers.put("defaultUriFetcher", fetcher); - return this; - } - - public JsonElementWrapper setSchemaFetchers(Map newFetchers) { - this.fetchers.clear(); - this.fetchers.putAll(newFetchers); - return this; - } - - public JsonElementWrapper addValidator(Validator validator) { - getValidators().add(validator); - return this; - } - - public LinkedHashSet getValidators() { - if (validators == null) { - validators = new LinkedHashSet(); - } - return validators; - } - - public boolean isDataValid() { - for (Validator validator : getValidators()) { - if (!validator.isValid(this.getJson())) - return false; - } - return true; - } - - public boolean validateData(StringBuilder sb) { - boolean res = true; - Iterator iterator = getValidators().iterator(); - System.out.println("JsonElementWrapper start validating "); - Validator validator; - while (iterator.hasNext()) { - validator = iterator.next(); - System.out.println("JsonElementWrapper validating using: " + validator.getTitle()); - - if (!validator.validate(this.getJson(), sb)) { - res = false; - } - } - return res; - } - - public MetaInfo fetchMetaInfo() { - if (metaInfo == null) - tryFetchMetaInfo(this.jsonSchemaUri); - return metaInfo; - } - - private void writeObject(ObjectOutputStream oos) throws IOException { - oos.defaultWriteObject(); - oos.writeObject(json.toString()); - } - - private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException, JsonException { - ois.defaultReadObject(); - this.wrap((T) JsonElement.readFrom((String) ois.readObject())); - } - - public String toString() { - return getJson().toString(); - } - +public abstract class JsonElementWrapper implements + ElementWrapper { + + private String contentType; + private transient LinkedTreeMap fetchers = new LinkedTreeMap(); + protected transient T json; + private URI jsonSchemaUri; + + private MetaInfo metaInfo; + private transient LinkedHashSet validators; + + public JsonElementWrapper() { + // todo do we need default fetcher ? + // fetchers.put("defaultUriFetcher", new SchemaUriFetcher()); + } + + public JsonElementWrapper(final T jsonElement) { + this.json = jsonElement; + } + + public JsonElementWrapper(final T jsonElement, final String contentType) { + this(jsonElement); + this.contentType = contentType; + + } + + public JsonElementWrapper(final T jsonElement, final String contentType, + final URI metaInfo) { + this(jsonElement, contentType); + this.jsonSchemaUri = metaInfo; + tryFetchMetaInfo(this.jsonSchemaUri); + } + + public JsonElementWrapper addSchemaFetcher(final String name, + final MetaInfoFetcher fetcher) { + this.fetchers.put(name, fetcher); + return this; + } + + public JsonElementWrapper addValidator(final Validator validator) { + getValidators().add(validator); + return this; + } + + private MetaInfo doFetchMetaInfo(final URI jsonSchemaUri) { + MetaInfo currSchema = null; + + Iterator> iterator = fetchers + .entrySet().iterator(); + while (iterator.hasNext() && currSchema == null) { + Map.Entry entry = iterator.next(); + System.out.println("JsonElementWrapper try fetch using: " + + entry.getKey()); + try { + currSchema = entry.getValue().fetch(jsonSchemaUri); + } catch (IOException e) { + e.printStackTrace(); + } + System.out.println("JsonElementWrapper fetch result: " + + ((currSchema == null) ? "FAIL" : "OK")); + } + return currSchema; + } + + public MetaInfo fetchMetaInfo() { + if (metaInfo == null) { + tryFetchMetaInfo(this.jsonSchemaUri); + } + return metaInfo; + } + + public String getContentType() { + return contentType; + } + + public MetaInfoFetcher getDefaultSchemaFetcher() { + return this.fetchers.get("defaultUriFetcher"); + } + + public T getJson() { + return json; + } + + public URI getJsonSchemaUri() { + return jsonSchemaUri; + } + + public MetaInfo getMetaInfo() { + return metaInfo; + } + + public LinkedHashSet getValidators() { + if (validators == null) { + validators = new LinkedHashSet(); + } + return validators; + } + + public boolean isDataValid() { + for (Validator validator : getValidators()) { + if (!validator.isValid(this.getJson())) { + return false; + } + } + return true; + } + + private void readObject(final ObjectInputStream ois) + throws ClassNotFoundException, IOException, JsonException { + ois.defaultReadObject(); + this.wrap((T) JsonElement.readFrom((String) ois.readObject())); + } + + public JsonElementWrapper setContentType(final String contentType) { + this.contentType = contentType; + return this; + } + + public JsonElementWrapper setDefaultSchemaFetcher( + final MetaInfoFetcher fetcher) { + this.fetchers.put("defaultUriFetcher", fetcher); + return this; + } + + public JsonElementWrapper setMetaInfo(final MetaInfo metaInfo) { + this.metaInfo = metaInfo; + return this; + } + + public JsonElementWrapper setMetaInfoUri(final URI uri) { + this.jsonSchemaUri = uri; + tryFetchMetaInfo(this.jsonSchemaUri); + return this; + } + + public JsonElementWrapper setSchemaFetchers( + final Map newFetchers) { + this.fetchers.clear(); + this.fetchers.putAll(newFetchers); + return this; + } + + @Override + public String toString() { + return getJson().toString(); + } + + /** + * Tries to fetch a schema and add the default Schema validator for it + * + * @param jsonSchemaUri + * @return + */ + private MetaInfo tryFetchMetaInfo(final URI jsonSchemaUri) { + if (jsonSchemaUri == null) { + return null; + } + try { + metaInfo = doFetchMetaInfo(jsonSchemaUri); + Validator validator = metaInfo.getDefaultValidator(); + if (validator != null) { + getValidators().add(validator); + } + } catch (Exception ex) { + return null; + } + + return metaInfo; + } + + public boolean validateData(final StringBuilder sb) { + boolean res = true; + Iterator iterator = getValidators().iterator(); + System.out.println("JsonElementWrapper start validating "); + Validator validator; + while (iterator.hasNext()) { + validator = iterator.next(); + System.out.println("JsonElementWrapper validating using: " + + validator.getTitle()); + + if (!validator.validate(this.getJson(), sb)) { + res = false; + } + } + return res; + } + + public J wrap(final T jsonElement) { + this.json = jsonElement; + return (J) this; + } + + private void writeObject(final ObjectOutputStream oos) throws IOException { + oos.defaultWriteObject(); + oos.writeObject(json.toString()); + } } diff --git a/json-wrapper/src/main/java/io/apptik/json/wrapper/TypedJsonArray.java b/json-wrapper/src/main/java/io/apptik/json/wrapper/TypedJsonArray.java index 26e5617..49c8557 100644 --- a/json-wrapper/src/main/java/io/apptik/json/wrapper/TypedJsonArray.java +++ b/json-wrapper/src/main/java/io/apptik/json/wrapper/TypedJsonArray.java @@ -1,298 +1,270 @@ package io.apptik.json.wrapper; - import io.apptik.json.JsonArray; import io.apptik.json.JsonElement; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; /** - * Helper class that can be used for Json Array containing always the same type of object; - * This is useful in the general case when elements are read from or written to Json once. - * however not efficient on many reads nor writes on same object. - * In the case of many reads and/or writes back use + * Helper class that can be used for Json Array containing always the same type + * of object; This is useful in the general case when elements are read from or + * written to Json once. however not efficient on many reads nor writes on same + * object. In the case of many reads and/or writes back use * {@link CachedTypedJsonArray} * - * @param The type + * @param + * The type */ -public abstract class TypedJsonArray extends JsonElementWrapper implements List { - - public TypedJsonArray() { - super(); - } - - @Override - public T wrap(JsonArray jsonElement) { - return super.wrap(jsonElement); - } - - @Override - public JsonArray getJson() { - if (super.getJson() == null) try { - this.json = JsonElement.readFrom("[]").asJsonArray(); - } catch (IOException e) { - e.printStackTrace(); - } - return super.getJson(); - } - - private T getInternal(JsonElement jsonElement, int pos) { - if (jsonElement == null) return null; - return get(jsonElement, pos); - } - - protected abstract T get(JsonElement jsonElement, int pos); - - protected abstract JsonElement to(T value); - - @Override - public int size() { - return getJson().length(); - } - - @Override - public boolean isEmpty() { - return getJson().isEmpty(); - } - - @Override - public boolean contains(Object o) { - return getJson().contains(JsonElement.wrap(o)); - } - - @Override - public Iterator iterator() { - final Iterator iterator = getJson().iterator(); - return new Iterator() { - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public T next() { - JsonElement el = iterator.next(); - return getInternal(el, indexOf(el)); - } - - @Override - public void remove() { - iterator.remove(); - } - }; - } - - @Override - public Object[] toArray() { - Object[] arr = new Object[getJson().size()]; - for (int i = 0; i < arr.length; i++) { - arr[i] = getInternal(getJson().get(i), i); - } - return arr; - } - - @Override - public T1[] toArray(T1[] t1s) { - Object[] elementArr = toArray(); - int size = getJson().size(); - if (t1s.length < size) - // Make a new array of a's runtime type, but my contents: - return (T1[]) Arrays.copyOf(elementArr, size, t1s.getClass()); - System.arraycopy(elementArr, 0, t1s, 0, size); - if (t1s.length > size) - t1s[size] = null; - return t1s; - } - - @Override - public boolean add(T t) { - getJson().put(to(t)); - return true; - } - - @Override - public boolean remove(Object o) { - if (!contains(o)) - return false; - getJson().remove(indexOf(o)); - return true; - } - - @Override - public boolean containsAll(Collection objects) { - return false; - } - - @Override - public boolean addAll(Collection ts) { - return false; - } - - @Override - public boolean addAll(int i, Collection ts) { - return false; - } - - @Override - public boolean removeAll(Collection objects) { - return false; - } - - @Override - public boolean retainAll(Collection objects) { - return false; - } - - @Override - public void clear() { - JsonElement.wrap(null); - } - - @Override - public T get(int i) { - return getInternal(getJson().asJsonArray().get(i), i); - } - - @Override - public T set(int i, T t) { - getJson().put(i, to(t)); - return t; - } - - @Override - public void add(int i, T t) { - getJson().put(i, to(t)); - } - - @Override - public T remove(int i) { - return getInternal(getJson().remove(i), i); - } - - @Override - public int indexOf(Object o) { - return getJson().indexOf(o); - } - - @Override - public int lastIndexOf(Object o) { - return getJson().lastIndexOf(o); - } - - @Override - public ListIterator listIterator() { - final ListIterator iterator = getJson().listIterator(); - return new ListIterator() { - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public T next() { - JsonElement el = iterator.next(); - return getInternal(el, indexOf(el)); - } - - @Override - public boolean hasPrevious() { - return iterator.hasPrevious(); - } - - @Override - public T previous() { - int pos = iterator.previousIndex(); - return getInternal(iterator.previous(), pos); - } - - @Override - public int nextIndex() { - return iterator.nextIndex(); - } - - @Override - public int previousIndex() { - return iterator.previousIndex(); - } - - @Override - public void remove() { - iterator.remove(); - } - - @Override - public void set(T t) { - iterator.set(to(t)); - } - - @Override - public void add(T t) { - iterator.add(to(t)); - } - }; - } - - @Override - public ListIterator listIterator(int i) { - final ListIterator iterator = getJson().listIterator(i); - return new ListIterator() { - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public T next() { - JsonElement el = iterator.next(); - return getInternal(el, indexOf(el)); - } - - @Override - public boolean hasPrevious() { - return iterator.hasPrevious(); - } - - @Override - public T previous() { - int pos = iterator.previousIndex(); - return getInternal(iterator.previous(), pos); - } - - @Override - public int nextIndex() { - return iterator.nextIndex(); - } - - @Override - public int previousIndex() { - return iterator.previousIndex(); - } - - @Override - public void remove() { - iterator.remove(); - } - - @Override - public void set(T t) { - iterator.set(to(t)); - } - - @Override - public void add(T t) { - iterator.add(to(t)); - } - }; - } - - @Override - public List subList(int i, int i2) { - List subList = getJson().subList(i, i2); - ArrayList resList = new ArrayList(); - for (JsonElement el : subList) { - resList.add(getInternal(el, indexOf(el))); - } - return resList; - } +public abstract class TypedJsonArray extends JsonElementWrapper + implements List { + + public TypedJsonArray() { + super(); + } + + public void add(final int i, final T t) { + getJson().put(i, to(t)); + } + + public boolean add(final T t) { + getJson().put(to(t)); + return true; + } + + public boolean addAll(final Collection ts) { + return false; + } + + public boolean addAll(final int i, final Collection ts) { + return false; + } + + public void clear() { + JsonElement.wrap(null); + } + + public boolean contains(final Object o) { + return getJson().contains(JsonElement.wrap(o)); + } + + public boolean containsAll(final Collection objects) { + return false; + } + + public T get(final int i) { + return getInternal(getJson().asJsonArray().get(i), i); + } + + protected abstract T get(JsonElement jsonElement, int pos); + + private T getInternal(final JsonElement jsonElement, final int pos) { + if (jsonElement == null) { + return null; + } + return get(jsonElement, pos); + } + + @Override + public JsonArray getJson() { + if (super.getJson() == null) { + try { + this.json = JsonElement.readFrom("[]").asJsonArray(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return super.getJson(); + } + + public int indexOf(final Object o) { + return getJson().indexOf(o); + } + + public boolean isEmpty() { + return getJson().isEmpty(); + } + + public Iterator iterator() { + final Iterator iterator = getJson().iterator(); + return new Iterator() { + + public boolean hasNext() { + return iterator.hasNext(); + } + + public T next() { + JsonElement el = iterator.next(); + return getInternal(el, indexOf(el)); + } + + public void remove() { + iterator.remove(); + } + }; + } + + public int lastIndexOf(final Object o) { + return getJson().lastIndexOf(o); + } + + public ListIterator listIterator() { + final ListIterator iterator = getJson().listIterator(); + return new ListIterator() { + + public void add(final T t) { + iterator.add(to(t)); + } + + public boolean hasNext() { + return iterator.hasNext(); + } + + public boolean hasPrevious() { + return iterator.hasPrevious(); + } + + public T next() { + JsonElement el = iterator.next(); + return getInternal(el, indexOf(el)); + } + + public int nextIndex() { + return iterator.nextIndex(); + } + + public T previous() { + int pos = iterator.previousIndex(); + return getInternal(iterator.previous(), pos); + } + + public int previousIndex() { + return iterator.previousIndex(); + } + + public void remove() { + iterator.remove(); + } + + public void set(final T t) { + iterator.set(to(t)); + } + }; + } + + public ListIterator listIterator(final int i) { + final ListIterator iterator = getJson().listIterator(i); + return new ListIterator() { + + public void add(final T t) { + iterator.add(to(t)); + } + + public boolean hasNext() { + return iterator.hasNext(); + } + + public boolean hasPrevious() { + return iterator.hasPrevious(); + } + + public T next() { + JsonElement el = iterator.next(); + return getInternal(el, indexOf(el)); + } + + public int nextIndex() { + return iterator.nextIndex(); + } + + public T previous() { + int pos = iterator.previousIndex(); + return getInternal(iterator.previous(), pos); + } + + public int previousIndex() { + return iterator.previousIndex(); + } + + public void remove() { + iterator.remove(); + } + + public void set(final T t) { + iterator.set(to(t)); + } + }; + } + + public T remove(final int i) { + return getInternal(getJson().remove(i), i); + } + + public boolean remove(final Object o) { + if (!contains(o)) { + return false; + } + getJson().remove(indexOf(o)); + return true; + } + + public boolean removeAll(final Collection objects) { + return false; + } + + public boolean retainAll(final Collection objects) { + return false; + } + + public T set(final int i, final T t) { + getJson().put(i, to(t)); + return t; + } + + public int size() { + return getJson().length(); + } + + public List subList(final int i, final int i2) { + List subList = getJson().subList(i, i2); + ArrayList resList = new ArrayList(); + for (JsonElement el : subList) { + resList.add(getInternal(el, indexOf(el))); + } + return resList; + } + + protected abstract JsonElement to(T value); + + public Object[] toArray() { + Object[] arr = new Object[getJson().size()]; + for (int i = 0; i < arr.length; i++) { + arr[i] = getInternal(getJson().get(i), i); + } + return arr; + } + + public T1[] toArray(final T1[] t1s) { + Object[] elementArr = toArray(); + int size = getJson().size(); + if (t1s.length < size) { + // Make a new array of a's runtime type, but my contents: + return (T1[]) Arrays.copyOf(elementArr, size, t1s.getClass()); + } + System.arraycopy(elementArr, 0, t1s, 0, size); + if (t1s.length > size) { + t1s[size] = null; + } + return t1s; + } + + @Override + public T wrap(final JsonArray jsonElement) { + return super.wrap(jsonElement); + } } diff --git a/json-wrapper/src/main/java/io/apptik/json/wrapper/TypedJsonObject.java b/json-wrapper/src/main/java/io/apptik/json/wrapper/TypedJsonObject.java index aacbefc..2829253 100644 --- a/json-wrapper/src/main/java/io/apptik/json/wrapper/TypedJsonObject.java +++ b/json-wrapper/src/main/java/io/apptik/json/wrapper/TypedJsonObject.java @@ -16,7 +16,6 @@ package io.apptik.json.wrapper; - import io.apptik.json.JsonElement; import io.apptik.json.JsonObject; import io.apptik.json.exception.JsonException; @@ -25,123 +24,124 @@ import java.util.Iterator; import java.util.Map; - /** - * Helper class that can be used for Json Objects containing always the same type; + * Helper class that can be used for Json Objects containing always the same + * type; * - * @param The type + * @param + * The type */ -public abstract class TypedJsonObject extends JsonObjectWrapper implements Iterable> { - - public T getValue(String key) throws JsonException { - return getInternal(getJson().get(key), key); - } - - - public T optValue(String key) { - return getInternal(getJson().opt(key), key); - } - - public T getValue(int pos) { - java.util.Collection var = getJson().valuesSet(); - return getInternal(var.toArray(new JsonObject[var.size()])[pos], getKey(pos)); - } - - public String getKey(int pos) { - try { - return getJson().names().getString(pos); - } catch (JsonException e) { - e.printStackTrace(); - return null; - } - } - - public > O putValue(String key, T value) throws JsonException { - getJson().put(key, to(value)); - return (O)this; - } - - public > O putAll(Map map) { - for(Map.Entry entry : map.entrySet()) { - try { - putValue(entry.getKey(),entry.getValue()); - } catch (JsonException e) { - e.printStackTrace(); - } - } - return (O)this; - } - - private T getInternal(JsonElement jsonElement, String key) { - if(jsonElement==null) return null; - return get(jsonElement, key); - } - - protected abstract T get(JsonElement jsonElement, String key); - protected abstract JsonElement to(T value); - - @Override - public Iterator> iterator() { - final Iterator> iterator = getJson().iterator(); - return new Iterator>() { - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public Map.Entry next() { - return new TypedObjectEntry(iterator.next()); - } - - @Override - public void remove() { - iterator.remove(); - } - }; - } - - public int length() { - return getJson().length(); - } - - public Map getEntries() { - Map res = new LinkedTreeMap(); - for (Map.Entry el : this) { - res.put(el.getKey(), el.getValue()); - } - return res; - } - - final class TypedObjectEntry implements Map.Entry { - private final String key; - private T value; - - public TypedObjectEntry(Map.Entry entry) { - this.key = entry.getKey(); - this.value = getInternal(entry.getValue(), key); - } - - public TypedObjectEntry(String key, T value) { - this.key = key; - this.value = value; - } - - @Override - public String getKey() { - return key; - } - - @Override - public T getValue() { - return value; - } - - @Override - public T setValue(T value) { - T old = this.value; - this.value = value; - return old; - } - } +public abstract class TypedJsonObject extends JsonObjectWrapper implements + Iterable> { + + final class TypedObjectEntry implements Map.Entry { + private final String key; + private T value; + + public TypedObjectEntry(final Map.Entry entry) { + this.key = entry.getKey(); + this.value = getInternal(entry.getValue(), key); + } + + public TypedObjectEntry(final String key, final T value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public T getValue() { + return value; + } + + public T setValue(final T value) { + T old = this.value; + this.value = value; + return old; + } + } + + protected abstract T get(JsonElement jsonElement, String key); + + public Map getEntries() { + Map res = new LinkedTreeMap(); + for (Map.Entry el : this) { + res.put(el.getKey(), el.getValue()); + } + return res; + } + + private T getInternal(final JsonElement jsonElement, final String key) { + if (jsonElement == null) { + return null; + } + return get(jsonElement, key); + } + + public String getKey(final int pos) { + try { + return getJson().names().getString(pos); + } catch (JsonException e) { + e.printStackTrace(); + return null; + } + } + + public T getValue(final int pos) { + java.util.Collection var = getJson().valuesSet(); + return getInternal(var.toArray(new JsonObject[var.size()])[pos], + getKey(pos)); + } + + public T getValue(final String key) throws JsonException { + return getInternal(getJson().get(key), key); + } + + public Iterator> iterator() { + final Iterator> iterator = getJson() + .iterator(); + return new Iterator>() { + + public boolean hasNext() { + return iterator.hasNext(); + } + + public Map.Entry next() { + return new TypedObjectEntry(iterator.next()); + } + + public void remove() { + iterator.remove(); + } + }; + } + + public int length() { + return getJson().length(); + } + + public T optValue(final String key) { + return getInternal(getJson().opt(key), key); + } + + public > O putAll(final Map map) { + for (Map.Entry entry : map.entrySet()) { + try { + putValue(entry.getKey(), entry.getValue()); + } catch (JsonException e) { + e.printStackTrace(); + } + } + return (O) this; + } + + public > O putValue(final String key, + final T value) throws JsonException { + getJson().put(key, to(value)); + return (O) this; + } + + protected abstract JsonElement to(T value); }