diff --git a/core/pom.xml b/core/pom.xml index cb7b42d89..0054cbcb5 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -139,6 +139,12 @@ json 20180130 + + org.glassfish + javax.json + 1.1.2 + true + com.google.guava guava diff --git a/core/src/main/java/org/everit/json/schema/ArraySchemaValidatingVisitor.java b/core/src/main/java/org/everit/json/schema/ArraySchemaValidatingVisitor.java index bf92e06cc..a3f15b112 100644 --- a/core/src/main/java/org/everit/json/schema/ArraySchemaValidatingVisitor.java +++ b/core/src/main/java/org/everit/json/schema/ArraySchemaValidatingVisitor.java @@ -1,7 +1,7 @@ package org.everit.json.schema; -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; +import org.everit.json.schema.spi.JsonAdaptation; +import org.everit.json.schema.spi.JsonArrayAdapter; import java.util.ArrayList; import java.util.Collection; @@ -10,7 +10,8 @@ import java.util.function.IntFunction; import java.util.stream.IntStream; -import org.json.JSONArray; +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; class ArraySchemaValidatingVisitor extends Visitor { @@ -18,20 +19,23 @@ class ArraySchemaValidatingVisitor extends Visitor { private final ValidatingVisitor owner; - private JSONArray arraySubject; + private final JsonAdaptation jsonAdaptation; + + private JsonArrayAdapter arraySubject; private ArraySchema arraySchema; private int subjectLength; - public ArraySchemaValidatingVisitor(Object subject, ValidatingVisitor owner) { + public ArraySchemaValidatingVisitor(Object subject, ValidatingVisitor owner, JsonAdaptation jsonAdaptation) { this.subject = subject; this.owner = requireNonNull(owner, "owner cannot be null"); + this.jsonAdaptation = jsonAdaptation; } @Override void visitArraySchema(ArraySchema arraySchema) { - if (owner.passesTypeCheck(JSONArray.class, arraySchema.requiresArray(), arraySchema.isNullable())) { - this.arraySubject = (JSONArray) subject; + if (owner.passesTypeCheck(jsonAdaptation.arrayType(), arraySchema.requiresArray(), arraySchema.isNullable())) { + this.arraySubject = (JsonArrayAdapter) jsonAdaptation.adapt(subject); this.subjectLength = arraySubject.length(); this.arraySchema = arraySchema; super.visitArraySchema(arraySchema); diff --git a/core/src/main/java/org/everit/json/schema/EnumSchema.java b/core/src/main/java/org/everit/json/schema/EnumSchema.java index 769dc15c9..f665d8eb6 100644 --- a/core/src/main/java/org/everit/json/schema/EnumSchema.java +++ b/core/src/main/java/org/everit/json/schema/EnumSchema.java @@ -9,6 +9,8 @@ import java.util.Set; import java.util.stream.Collectors; import org.everit.json.schema.internal.JSONPrinter; +import org.everit.json.schema.spi.JsonArrayAdapter; +import org.everit.json.schema.spi.JsonObjectAdapter; import org.json.JSONArray; import org.json.JSONObject; @@ -18,11 +20,15 @@ public class EnumSchema extends Schema { static Object toJavaValue(Object orig) { - if (orig instanceof JSONArray) { + if (orig instanceof JsonArrayAdapter) { + return ((JsonArrayAdapter) orig).toList(); + } else if (orig instanceof JsonObjectAdapter) { + return ((JsonObjectAdapter) orig).toMap(); + } else if (orig instanceof JSONArray) { // recognize this type to support test cases that don't adapt it return ((JSONArray) orig).toList(); - } else if (orig instanceof JSONObject) { + } else if (orig instanceof JSONObject) { // recognize this type to support test cases that don't adapt it return ((JSONObject) orig).toMap(); - } else if (orig == JSONObject.NULL) { + } else if (orig == JSONObject.NULL) { // recognize this value to support test cases that don't adapt it return null; } else { return orig; diff --git a/core/src/main/java/org/everit/json/schema/JSONAdaptation.java b/core/src/main/java/org/everit/json/schema/JSONAdaptation.java new file mode 100644 index 000000000..0f5fbcce2 --- /dev/null +++ b/core/src/main/java/org/everit/json/schema/JSONAdaptation.java @@ -0,0 +1,72 @@ +package org.everit.json.schema; + +import org.everit.json.schema.spi.JsonAdaptation; +import org.everit.json.schema.spi.JsonAdapter; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.stream.Stream; + +/** + * A {@link JsonAdaptation} that uses {@link JSONArray} as the array type and + * {@link JSONObject} as the object type. + * + */ +class JSONAdaptation implements JsonAdaptation { + + private static final Class[] SUPPORTED_TYPES = { + JSONArray.class, + JSONObject.class, + JSONObject.NULL.getClass() + }; + + @Override + public Class arrayType() { + return JSONArray.class; + } + + @Override + public Class objectType() { + return JSONObject.class; + } + + @Override + public Class[] supportedTypes() { + return SUPPORTED_TYPES; + } + + @Override + public boolean isSupportedType(Class type) { + return Stream.of(SUPPORTED_TYPES).anyMatch(t -> t.isAssignableFrom(type)); + } + + @Override + public boolean isNull(Object value) { + return value == null || JSONObject.NULL.equals(value); + } + + @Override + public Object adapt(Object value) { + if (JSONObject.NULL.equals(value)) { + return null; + } else if (value instanceof JSONArray) { + return new JSONArrayAdapter((JSONArray) value); + } else if (value instanceof JSONObject) { + return new JSONObjectAdapter((JSONObject) value); + } else { + return value; + } + } + + @Override + public Object invert(Object value) { + if (value == null) { + return JSONObject.NULL; + } else if (value instanceof JsonAdapter) { + return ((JsonAdapter) value).unwrap(); + } else { + return value; + } + } + +} diff --git a/core/src/main/java/org/everit/json/schema/JSONArrayAdapter.java b/core/src/main/java/org/everit/json/schema/JSONArrayAdapter.java new file mode 100644 index 000000000..e6e22aa04 --- /dev/null +++ b/core/src/main/java/org/everit/json/schema/JSONArrayAdapter.java @@ -0,0 +1,42 @@ +package org.everit.json.schema; + +import org.everit.json.schema.spi.JsonArrayAdapter; +import org.json.JSONArray; + +import java.util.List; + +/** + * A {@link JsonArrayAdapter} that delegates to a {@link JSONArray}. + */ +class JSONArrayAdapter implements JsonArrayAdapter { + + private final JSONArray delegate; + + JSONArrayAdapter(JSONArray delegate) { + this.delegate = delegate; + } + + @Override + public Object unwrap() { + return delegate; + } + + public Object get(int index) { + return delegate.get(index); + } + + public int length() { + return delegate.length(); + } + + @Override + public void put(int index, Object value) { + delegate.put(index, value); + } + + @Override + public List toList() { + return delegate.toList(); + } + +} diff --git a/core/src/main/java/org/everit/json/schema/JSONObjectAdapter.java b/core/src/main/java/org/everit/json/schema/JSONObjectAdapter.java new file mode 100644 index 000000000..890a7f307 --- /dev/null +++ b/core/src/main/java/org/everit/json/schema/JSONObjectAdapter.java @@ -0,0 +1,54 @@ +package org.everit.json.schema; + +import org.everit.json.schema.spi.JsonObjectAdapter; +import org.json.JSONObject; + +import java.util.Map; + +/** + * A {@link JsonObjectAdapter} that delegates to a {@link JSONObject}. + */ +class JSONObjectAdapter implements JsonObjectAdapter { + + private final JSONObject delegate; + + JSONObjectAdapter(JSONObject delegate) { + this.delegate = delegate; + } + + @Override + public Object unwrap() { + return delegate; + } + + @Override + public int length() { + return delegate.length(); + } + + @Override + public String[] keys() { + return JSONObject.getNames(delegate); + } + + @Override + public boolean has(String key) { + return delegate.has(key); + } + + @Override + public Object get(String key) { + return delegate.get(key); + } + + @Override + public void put(String key, Object value) { + delegate.put(key, value); + } + + @Override + public Map toMap() { + return delegate.toMap(); + } + +} diff --git a/core/src/main/java/org/everit/json/schema/NumberSchemaValidatingVisitor.java b/core/src/main/java/org/everit/json/schema/NumberSchemaValidatingVisitor.java index 937091a32..f1ad1601e 100644 --- a/core/src/main/java/org/everit/json/schema/NumberSchemaValidatingVisitor.java +++ b/core/src/main/java/org/everit/json/schema/NumberSchemaValidatingVisitor.java @@ -1,5 +1,7 @@ package org.everit.json.schema; +import org.everit.json.schema.spi.JsonAdaptation; + import static java.lang.String.format; import java.math.BigDecimal; @@ -18,15 +20,18 @@ class NumberSchemaValidatingVisitor extends Visitor { private final ValidatingVisitor owner; + private final JsonAdaptation jsonAdaptation; + private boolean exclusiveMinimum; private boolean exclusiveMaximum; private double numberSubject; - NumberSchemaValidatingVisitor(Object subject, ValidatingVisitor owner) { + NumberSchemaValidatingVisitor(Object subject, ValidatingVisitor owner, JsonAdaptation jsonAdaptation) { this.subject = subject; this.owner= owner; + this.jsonAdaptation = jsonAdaptation; } @Override void visitNumberSchema(NumberSchema numberSchema) { @@ -34,7 +39,7 @@ class NumberSchemaValidatingVisitor extends Visitor { if (!INTEGRAL_TYPES.contains(subject.getClass()) && numberSchema.requiresInteger()) { owner.failure(Integer.class, subject); } else { - this.numberSubject = ((Number) subject).doubleValue(); + this.numberSubject = ((Number) jsonAdaptation.adapt(subject)).doubleValue(); super.visitNumberSchema(numberSchema); } } diff --git a/core/src/main/java/org/everit/json/schema/ObjectComparator.java b/core/src/main/java/org/everit/json/schema/ObjectComparator.java index e8984e7d6..13e8b87f7 100644 --- a/core/src/main/java/org/everit/json/schema/ObjectComparator.java +++ b/core/src/main/java/org/everit/json/schema/ObjectComparator.java @@ -1,13 +1,17 @@ package org.everit.json.schema; -import java.util.Arrays; -import java.util.Objects; +import java.util.*; +import org.everit.json.schema.spi.JsonArrayAdapter; +import org.everit.json.schema.spi.JsonObjectAdapter; import org.json.JSONArray; import org.json.JSONObject; /** - * Deep-equals implementation on primitive wrappers, {@link JSONObject} and {@link JSONArray}. + * Deep-equals implementation on primitive wrappers, array and object adapters, and + * {@link JSONObject} and {@link JSONArray}. {@link JSONArray} and {@link JSONObject} are + * included in order to support test cases without the need to first wrap in an adapter. + * */ public final class ObjectComparator { @@ -21,21 +25,32 @@ public final class ObjectComparator { * @return {@code true} if the two objects are equal, {@code false} otherwise */ public static boolean deepEquals(Object obj1, Object obj2) { - if (obj1 instanceof JSONArray) { - if (!(obj2 instanceof JSONArray)) { + if (obj1 instanceof JsonArrayAdapter) { + if (!(obj2 instanceof JsonArrayAdapter)) { return false; } - return deepEqualArrays((JSONArray) obj1, (JSONArray) obj2); - } else if (obj1 instanceof JSONObject) { - if (!(obj2 instanceof JSONObject)) { + return deepEqualArrays((JsonArrayAdapter) obj1, (JsonArrayAdapter) obj2); + } else if (obj1 instanceof JsonObjectAdapter) { + if (!(obj2 instanceof JsonObjectAdapter)) { return false; } - return deepEqualObjects((JSONObject) obj1, (JSONObject) obj2); + return deepEqualObjects((JsonObjectAdapter) obj1, (JsonObjectAdapter) obj2); + } else if (obj1 instanceof JSONArray) { // continue to recognize the JSONArray type to support + if (!(obj2 instanceof JSONArray)) { // test cases that don't adapt org.json types + return false; + } + return deepEqualArrays(new JSONArrayAdapter((JSONArray) obj1), new JSONArrayAdapter((JSONArray) obj2)); + } else if (obj1 instanceof JSONObject) { // continue to recognize the JSONArray type to support + if (!(obj2 instanceof JSONObject)) { // test cases that don't adapt org.json types + return false; + } + return deepEqualObjects(new JSONObjectAdapter((JSONObject) obj1), new JSONObjectAdapter((JSONObject) obj2)); } + return Objects.equals(obj1, obj2); } - private static boolean deepEqualArrays(JSONArray arr1, JSONArray arr2) { + private static boolean deepEqualArrays(JsonArrayAdapter arr1, JsonArrayAdapter arr2) { if (arr1.length() != arr2.length()) { return false; } @@ -47,8 +62,8 @@ private static boolean deepEqualArrays(JSONArray arr1, JSONArray arr2) { return true; } - private static String[] sortedNamesOf(JSONObject obj) { - String[] raw = JSONObject.getNames(obj); + private static String[] sortedNamesOf(JsonObjectAdapter obj) { + String[] raw = obj.keys(); if (raw == null) { return null; } @@ -56,7 +71,8 @@ private static String[] sortedNamesOf(JSONObject obj) { return raw; } - private static boolean deepEqualObjects(JSONObject jsonObj1, JSONObject jsonObj2) { + private static boolean deepEqualObjects(JsonObjectAdapter jsonObj1, + JsonObjectAdapter jsonObj2) { String[] obj1Names = sortedNamesOf(jsonObj1); if (!Arrays.equals(obj1Names, sortedNamesOf(jsonObj2))) { return false; diff --git a/core/src/main/java/org/everit/json/schema/ObjectSchemaValidatingVisitor.java b/core/src/main/java/org/everit/json/schema/ObjectSchemaValidatingVisitor.java index 32647c43e..9a4a6cba0 100644 --- a/core/src/main/java/org/everit/json/schema/ObjectSchemaValidatingVisitor.java +++ b/core/src/main/java/org/everit/json/schema/ObjectSchemaValidatingVisitor.java @@ -8,13 +8,14 @@ import java.util.Set; import org.everit.json.schema.regexp.Regexp; -import org.json.JSONObject; +import org.everit.json.schema.spi.JsonAdaptation; +import org.everit.json.schema.spi.JsonObjectAdapter; class ObjectSchemaValidatingVisitor extends Visitor { private final Object subject; - private JSONObject objSubject; + private JsonObjectAdapter objSubject; private ObjectSchema schema; @@ -22,14 +23,18 @@ class ObjectSchemaValidatingVisitor extends Visitor { private final ValidatingVisitor owner; - public ObjectSchemaValidatingVisitor(Object subject, ValidatingVisitor owner) { + private final JsonAdaptation jsonAdaptation; + + public ObjectSchemaValidatingVisitor(Object subject, ValidatingVisitor owner, JsonAdaptation jsonAdaptation) { this.subject = requireNonNull(subject, "subject cannot be null"); this.owner = requireNonNull(owner, "owner cannot be null"); + this.jsonAdaptation = jsonAdaptation; } @Override void visitObjectSchema(ObjectSchema objectSchema) { - if (owner.passesTypeCheck(JSONObject.class, objectSchema.requiresObject(), objectSchema.isNullable())) { - objSubject = (JSONObject) subject; + if (owner.passesTypeCheck(jsonAdaptation.objectType(), objectSchema.requiresObject(), + objectSchema.isNullable())) { + objSubject = (JsonObjectAdapter) jsonAdaptation.adapt(subject); objectSize = objSubject.length(); this.schema = objectSchema; super.visitObjectSchema(objectSchema); @@ -44,7 +49,7 @@ public ObjectSchemaValidatingVisitor(Object subject, ValidatingVisitor owner) { @Override void visitPropertyNameSchema(Schema propertyNameSchema) { if (propertyNameSchema != null) { - String[] names = JSONObject.getNames(objSubject); + String[] names = objSubject.keys(); if (names == null || names.length == 0) { return; } @@ -105,7 +110,7 @@ public ObjectSchemaValidatingVisitor(Object subject, ValidatingVisitor owner) { } private List getAdditionalProperties() { - String[] names = JSONObject.getNames(objSubject); + String[] names = objSubject.keys(); if (names == null) { return new ArrayList<>(); } else { @@ -129,7 +134,7 @@ private boolean matchesAnyPattern(String key) { } @Override void visitPatternPropertySchema(Regexp propertyNamePattern, Schema schema) { - String[] propNames = JSONObject.getNames(objSubject); + String[] propNames = objSubject.keys(); if (propNames == null || propNames.length == 0) { return; } @@ -152,14 +157,17 @@ private boolean matchesAnyPattern(String key) { } } - @Override void visitPropertySchema(String properyName, Schema schema) { - if (objSubject.has(properyName)) { - ValidationException failure = owner.getFailureOfSchema(schema, objSubject.get(properyName)); + @SuppressWarnings("unchecked") + @Override void visitPropertySchema(String propertyName, Schema schema) { + if (objSubject.has(propertyName)) { + ValidationException failure = owner.getFailureOfSchema(schema, objSubject.get(propertyName)); if (failure != null) { - owner.failure(failure.prepend(properyName)); + owner.failure(failure.prepend(propertyName)); } } else if (schema.hasDefaultValue()) { - objSubject.put(properyName, schema.getDefaultValue()); + // we're using the raw type here under the assumption that the object adapter and + // the adaptation that produced it are using the same type T. + objSubject.put(propertyName, jsonAdaptation.invert(schema.getDefaultValue())); } } } diff --git a/core/src/main/java/org/everit/json/schema/StringSchemaValidatingVisitor.java b/core/src/main/java/org/everit/json/schema/StringSchemaValidatingVisitor.java index c32ebbe65..289f8de74 100644 --- a/core/src/main/java/org/everit/json/schema/StringSchemaValidatingVisitor.java +++ b/core/src/main/java/org/everit/json/schema/StringSchemaValidatingVisitor.java @@ -6,6 +6,7 @@ import java.util.Optional; import org.everit.json.schema.regexp.Regexp; +import org.everit.json.schema.spi.JsonAdaptation; public class StringSchemaValidatingVisitor extends Visitor { @@ -17,14 +18,17 @@ public class StringSchemaValidatingVisitor extends Visitor { private final ValidatingVisitor owner; - public StringSchemaValidatingVisitor(Object subject, ValidatingVisitor owner) { + private final JsonAdaptation jsonAdaptation; + + public StringSchemaValidatingVisitor(Object subject, ValidatingVisitor owner, JsonAdaptation jsonAdaptation) { this.subject = subject; this.owner = requireNonNull(owner, "failureReporter cannot be null"); + this.jsonAdaptation = requireNonNull(jsonAdaptation, "jsonAdaptation cannot be null"); } @Override void visitStringSchema(StringSchema stringSchema) { if (owner.passesTypeCheck(String.class, stringSchema.requireString(), stringSchema.isNullable())) { - stringSubject = (String) subject; + stringSubject = (String) jsonAdaptation.adapt(subject); stringLength = stringSubject.codePointCount(0, stringSubject.length()); super.visitStringSchema(stringSchema); } diff --git a/core/src/main/java/org/everit/json/schema/ValidatingVisitor.java b/core/src/main/java/org/everit/json/schema/ValidatingVisitor.java index 8795b15ca..2e4acb67b 100644 --- a/core/src/main/java/org/everit/json/schema/ValidatingVisitor.java +++ b/core/src/main/java/org/everit/json/schema/ValidatingVisitor.java @@ -1,35 +1,27 @@ package org.everit.json.schema; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static java.util.Collections.unmodifiableList; -import static java.util.stream.Collectors.joining; -import static org.everit.json.schema.EnumSchema.toJavaValue; +import org.everit.json.schema.spi.JsonAdaptation; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Stream; -import org.json.JSONArray; -import org.json.JSONObject; +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.unmodifiableList; +import static java.util.stream.Collectors.joining; +import static org.everit.json.schema.EnumSchema.toJavaValue; class ValidatingVisitor extends Visitor { - private static final List> VALIDATED_TYPES = unmodifiableList(asList( + private static final List> INTRINSIC_TYPES = unmodifiableList(asList( Number.class, String.class, - Boolean.class, - JSONObject.class, - JSONArray.class, - JSONObject.NULL.getClass() + Boolean.class )); - static final String TYPE_FAILURE_MSG = "subject is an instance of non-handled type %s. Should be one of " - + VALIDATED_TYPES.stream().map(Class::getSimpleName).collect(joining(", ")); - - private static boolean isNull(Object obj) { - return obj == null || JSONObject.NULL.equals(obj); - } + static final String TYPE_FAILURE_MSG = "subject is an instance of non-handled type %s. Should be one of %s."; protected Object subject; @@ -37,6 +29,12 @@ private static boolean isNull(Object obj) { private final ReadWriteValidator readWriteValidator; + private final JsonAdaptation jsonAdaptation; + + private boolean isNull(Object obj) { + return obj == null || jsonAdaptation.isNull(obj); + } + @Override void visit(Schema schema) { if (schema.isNullable() == Boolean.FALSE && isNull(subject)) { @@ -46,23 +44,29 @@ void visit(Schema schema) { super.visit(schema); } - ValidatingVisitor(Object subject, ValidationFailureReporter failureReporter, ReadWriteValidator readWriteValidator) { - if (subject != null && !VALIDATED_TYPES.stream().anyMatch(type -> type.isAssignableFrom(subject.getClass()))) { - throw new IllegalArgumentException(format(TYPE_FAILURE_MSG, subject.getClass().getSimpleName())); + ValidatingVisitor(Object subject, ValidationFailureReporter failureReporter, + ReadWriteValidator readWriteValidator, JsonAdaptation jsonAdaptation) { + if (subject != null + && !INTRINSIC_TYPES.stream().anyMatch(type -> type.isAssignableFrom(subject.getClass())) + && !jsonAdaptation.isSupportedType(subject.getClass())) { + throw new IllegalArgumentException(format(TYPE_FAILURE_MSG, subject.getClass().getSimpleName(), + Stream.concat(INTRINSIC_TYPES.stream(), Stream.of(jsonAdaptation.supportedTypes())) + .map(Class::getSimpleName).collect(joining(", ")))); } this.subject = subject; + this.jsonAdaptation = jsonAdaptation; this.failureReporter = failureReporter; this.readWriteValidator = readWriteValidator; } @Override void visitNumberSchema(NumberSchema numberSchema) { - numberSchema.accept(new NumberSchemaValidatingVisitor(subject, this)); + numberSchema.accept(new NumberSchemaValidatingVisitor(subject, this, jsonAdaptation)); } @Override void visitArraySchema(ArraySchema arraySchema) { - arraySchema.accept(new ArraySchemaValidatingVisitor(subject, this)); + arraySchema.accept(new ArraySchemaValidatingVisitor(subject, this, jsonAdaptation)); } @Override @@ -74,7 +78,7 @@ void visitBooleanSchema(BooleanSchema schema) { @Override void visitNullSchema(NullSchema nullSchema) { - if (!(subject == null || subject == JSONObject.NULL)) { + if (!(subject == null || jsonAdaptation.isNull(subject))) { failureReporter.failure("expected: null, found: " + subject.getClass().getSimpleName(), "type"); } } @@ -84,7 +88,7 @@ void visitConstSchema(ConstSchema constSchema) { if (isNull(subject) && isNull(constSchema.getPermittedValue())) { return; } - Object effectiveSubject = toJavaValue(subject); + Object effectiveSubject = toJavaValue(jsonAdaptation.adapt(subject)); if (!ObjectComparator.deepEquals(effectiveSubject, constSchema.getPermittedValue())) { failureReporter.failure("", "const"); } @@ -92,7 +96,7 @@ void visitConstSchema(ConstSchema constSchema) { @Override void visitEnumSchema(EnumSchema enumSchema) { - Object effectiveSubject = toJavaValue(subject); + Object effectiveSubject = toJavaValue(jsonAdaptation.adapt(subject)); for (Object possibleValue : enumSchema.getPossibleValues()) { if (ObjectComparator.deepEquals(possibleValue, effectiveSubject)) { return; @@ -129,12 +133,12 @@ void visitReferenceSchema(ReferenceSchema referenceSchema) { @Override void visitObjectSchema(ObjectSchema objectSchema) { - objectSchema.accept(new ObjectSchemaValidatingVisitor(subject, this)); + objectSchema.accept(new ObjectSchemaValidatingVisitor(subject, this, jsonAdaptation)); } @Override void visitStringSchema(StringSchema stringSchema) { - stringSchema.accept(new StringSchemaValidatingVisitor(subject, this)); + stringSchema.accept(new StringSchemaValidatingVisitor(subject, this, jsonAdaptation)); } @Override @@ -168,7 +172,9 @@ void visitConditionalSchema(ConditionalSchema conditionalSchema) { ValidationException getFailureOfSchema(Schema schema, Object input) { Object origSubject = this.subject; - this.subject = input; + // TODO: performance could be improved by revisiting test cases that break when + // the adaptation value is not inverted here + this.subject = jsonAdaptation.invert(input); ValidationException rval = failureReporter.inContextOfSchema(schema, () -> visit(schema)); this.subject = origSubject; return rval; diff --git a/core/src/main/java/org/everit/json/schema/Validator.java b/core/src/main/java/org/everit/json/schema/Validator.java index b780b8995..5238e206c 100644 --- a/core/src/main/java/org/everit/json/schema/Validator.java +++ b/core/src/main/java/org/everit/json/schema/Validator.java @@ -1,5 +1,7 @@ package org.everit.json.schema; +import org.everit.json.schema.spi.JsonAdaptation; + import java.util.function.BiFunction; public interface Validator { @@ -10,6 +12,8 @@ class ValidatorBuilder { private ReadWriteContext readWriteContext; + private JsonAdaptation jsonAdaptation = new JSONAdaptation(); + public ValidatorBuilder failEarly() { this.failEarly = true; return this; @@ -20,8 +24,13 @@ public ValidatorBuilder readWriteContext(ReadWriteContext readWriteContext) { return this; } + public ValidatorBuilder jsonAdaptation(JsonAdaptation jsonAdaptation) { + this.jsonAdaptation = jsonAdaptation; + return this; + } + public Validator build() { - return new DefaultValidator(failEarly, readWriteContext); + return new DefaultValidator(failEarly, readWriteContext, jsonAdaptation); } } @@ -41,15 +50,18 @@ class DefaultValidator implements Validator { private final ReadWriteContext readWriteContext; - DefaultValidator(boolean failEarly, ReadWriteContext readWriteContext) { + private final JsonAdaptation jsonAdaptation; + + DefaultValidator(boolean failEarly, ReadWriteContext readWriteContext, JsonAdaptation jsonAdaptation) { this.failEarly = failEarly; this.readWriteContext = readWriteContext; + this.jsonAdaptation = jsonAdaptation; } @Override public void performValidation(Schema schema, Object input) { ValidationFailureReporter failureReporter = createFailureReporter(schema); ReadWriteValidator readWriteValidator = ReadWriteValidator.createForContext(readWriteContext, failureReporter); - ValidatingVisitor visitor = new ValidatingVisitor(input, failureReporter, readWriteValidator); + ValidatingVisitor visitor = new ValidatingVisitor(input, failureReporter, readWriteValidator, jsonAdaptation); visitor.visit(schema); visitor.failIfErrorFound(); } diff --git a/core/src/main/java/org/everit/json/schema/javax/json/JavaxJsonAdaptation.java b/core/src/main/java/org/everit/json/schema/javax/json/JavaxJsonAdaptation.java new file mode 100644 index 000000000..9065f32fe --- /dev/null +++ b/core/src/main/java/org/everit/json/schema/javax/json/JavaxJsonAdaptation.java @@ -0,0 +1,235 @@ +package org.everit.json.schema.javax.json; + +import org.everit.json.schema.spi.JsonAdaptation; +import org.everit.json.schema.spi.JsonAdapter; + +import javax.json.*; +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * A {@link JsonAdaptation} for the standard Java JSON types in the {@code javax.json} + * package. + *

+ * The current specification for these types is part of the JSON-P specification + * (JSR-374), which is part of the Java EE 8 platform. An earlier version of the JSON-P + * specification (JSR-353) was used in the Java EE 7 platform. + *

+ * Because the later specification provides some improvements to the API needed to support + * these types with JSON schema validation, we want to use the later version where possible. + * However, most Java EE 7 containers will have the older JSR-353 API. + *

+ * Using the {@link #newInstance()} method, a user of this adaptation can get an instance + * that uses the latest version of the JSON-P that is available at runtime. + */ +public abstract class JavaxJsonAdaptation implements JsonAdaptation { + + static final String JSR_374_ADAPTATION = "Jsr374Adaptation"; + static final String JSR_353_ADAPTATION = "Jsr353Adaptation"; + + @Override + public Class arrayType() { + return JsonArray.class; + } + + @Override + public Class objectType() { + return JsonObject.class; + } + + @Override + public Class[] supportedTypes() { + return new Class[] { JsonValue.class }; + } + + /** + * Creates a new instance of an adaptation for the standard JSON types using the + * latest version of the JSON-P implementation that is available via either the + * thread context class loader (if one is set on the calling thread) or from the + * same class loader that loaded this class. + * + * @return adaptation instance for JSON-P data types + * @throws RuntimeException if the adaptation class cannot be instantiated + */ + public static JavaxJsonAdaptation newInstance() { + if (Thread.currentThread().getContextClassLoader() != null) { + return newInstance(Thread.currentThread().getContextClassLoader()); + } + return newInstance(JavaxJsonAdaptation.class.getClassLoader()); + } + + /** + * Creates a new instance of an adaptation for the standard JSON types using the + * latest version of the JSON-P implementation that is available on the specified + * class loader. + *

+ * This method may be use in dynamic modular runtime environments such as those + * provided by OSGi. + * + * @param classLoader the class loader to use to find the JSON-P API and + * implementation classes + * @return adaptation instance for JSON-P data types + * @throws RuntimeException if the adaptation class cannot be instantiated + */ + public static JavaxJsonAdaptation newInstance(ClassLoader classLoader) { + return newInstance(classLoader, determineProvider("createValue", String.class)); + } + + /** + * Creates a new adaptation instance using the given class loader to load a + * specific provider name. + * @param classLoader source class loader for the JSON-P types + * @param providerName name of the {@link JavaxJsonAdaptation} to load + * @return adaptation instance + * @throws RuntimeException an instance of the specified type cannot be instantiated + */ + static JavaxJsonAdaptation newInstance(ClassLoader classLoader, + String providerName) { + try { + return (JavaxJsonAdaptation) classLoader.loadClass( + providerClassName(providerName)).newInstance(); + } + catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Determine the name of the adaptation provider class based on the availability + * of a particular sentinel method in the {@link Json} class. + * @param sentinelMethodName name of the method whose presence is to be checked + * @param argTypes argument types for the sentinel method + * @return adaptation provider name + */ + static String determineProvider(String sentinelMethodName, Class... argTypes) { + try { + Json.class.getMethod(sentinelMethodName, argTypes); + return JSR_374_ADAPTATION; + } catch (NoSuchMethodException ex) { + return JSR_353_ADAPTATION; + } + } + + /** + * Constructs the fully-qualified class name for a given named provider class. + *

+ * It is assumed that all providers are subtypes of this abstract base type and + * are located in the same package. + * + * @param providerName provider class name + * @return fully qualified class name + */ + private static String providerClassName(String providerName) { + return JavaxJsonAdaptation.class.getPackage().getName() + "." + providerName; + } + + @Override + public boolean isSupportedType(Class type) { + return JsonValue.class.isAssignableFrom(type); + } + + @Override + public boolean isNull(Object value) { + return value == null || value == JsonValue.NULL; + } + + @Override + public Object adapt(Object value) { + if (value == JsonValue.NULL) { + return null; + } else if (value == JsonValue.TRUE) { + return true; + } else if (value == JsonValue.FALSE) { + return false; + } else if (value instanceof JsonString) { + return ((JsonString) value).getString(); + } else if (value instanceof JsonNumber) { + if (((JsonNumber) value).isIntegral()) { + return ((JsonNumber) value).bigIntegerValue(); + } else { + return ((JsonNumber) value).bigDecimalValue(); + } + } else if (value instanceof JsonArray) { + return new JavaxJsonArrayAdapter((JsonArray) value); + } else if (value instanceof JsonObject) { + return new JavaxJsonObjectAdapter((JsonObject) value); + } else { + return value; + } + } + + @Override + public JsonValue invert(Object value) { + if (value == null) { + return JsonValue.NULL; + } + if (value instanceof JsonAdapter) { + return (JsonValue) ((JsonAdapter) value).unwrap(); + } else if (value instanceof String) { + return createValue((String) value); + } else if (value instanceof Boolean) { + return ((boolean) value) ? JsonValue.TRUE : JsonValue.FALSE; + } else if (value instanceof Integer || value instanceof Byte || value instanceof Short) { + return createValue((int) value); + } else if (value instanceof Long) { + return createValue((long) value); + } else if (value instanceof Double || value instanceof Float) { + return createValue((double) value); + } else if (value instanceof BigInteger) { + return createValue((BigInteger) value); + } else if (value instanceof BigDecimal) { + return createValue((BigDecimal) value); + } else { + throw new AssertionError("unrecognized intrinsic type"); + } + } + + /** + * Creates a {@link JsonValue} representing the given string using a provider-specific + * mechanism. + * @param value the subject string value + * @return JSON value instance + */ + abstract JsonValue createValue(String value); + + /** + * Creates a {@link JsonValue} representing the given integer using a provider-specific + * mechanism. + * @param value the subject integer value + * @return JSON value instance + */ + abstract JsonValue createValue(int value); + + /** + * Creates a {@link JsonValue} representing the given long using a provider-specific + * mechanism. + * @param value the subject long value + * @return JSON value instance + */ + abstract JsonValue createValue(long value); + + /** + * Creates a {@link JsonValue} representing the given double using a provider-specific + * mechanism. + * @param value the subject double value + * @return JSON value instance + */ + abstract JsonValue createValue(double value); + + /** + * Creates a {@link JsonValue} representing the given big integer using a + * provider-specific mechanism. + * @param value the subject big integer value + * @return JSON value instance + */ + abstract JsonValue createValue(BigInteger value); + + /** + * Creates a {@link JsonValue} representing the given big decimal using a + * provider-specific mechanism. + * @param value the subject big decimal value + * @return JSON value instance + */ + abstract JsonValue createValue(BigDecimal value); + +} diff --git a/core/src/main/java/org/everit/json/schema/javax/json/JavaxJsonArrayAdapter.java b/core/src/main/java/org/everit/json/schema/javax/json/JavaxJsonArrayAdapter.java new file mode 100644 index 000000000..31f9851d6 --- /dev/null +++ b/core/src/main/java/org/everit/json/schema/javax/json/JavaxJsonArrayAdapter.java @@ -0,0 +1,46 @@ +package org.everit.json.schema.javax.json; + +import org.everit.json.schema.spi.JsonArrayAdapter; + +import javax.json.JsonArray; +import javax.json.JsonValue; +import java.util.Collections; +import java.util.List; + +/** + * A {@link JsonArrayAdapter} that delegates to a JSON-P {@link JsonArray}. + */ +class JavaxJsonArrayAdapter implements JsonArrayAdapter { + + private final JsonArray delegate; + + JavaxJsonArrayAdapter(JsonArray delegate) { + this.delegate = delegate; + } + + @Override + public int length() { + return delegate.size(); + } + + @Override + public JsonValue get(int index) { + return delegate.get(index); + } + + @Override + public void put(int index, JsonValue value) { + throw new UnsupportedOperationException(); + } + + @Override + public List toList() { + return Collections.unmodifiableList(delegate); + } + + @Override + public JsonValue unwrap() { + return delegate; + } + +} diff --git a/core/src/main/java/org/everit/json/schema/javax/json/JavaxJsonObjectAdapter.java b/core/src/main/java/org/everit/json/schema/javax/json/JavaxJsonObjectAdapter.java new file mode 100644 index 000000000..75a79b6f0 --- /dev/null +++ b/core/src/main/java/org/everit/json/schema/javax/json/JavaxJsonObjectAdapter.java @@ -0,0 +1,56 @@ +package org.everit.json.schema.javax.json; + +import org.everit.json.schema.spi.JsonObjectAdapter; + +import javax.json.JsonObject; +import javax.json.JsonValue; +import java.util.Collections; +import java.util.Map; + +/** + * A {@link JsonObjectAdapter} that delegates to a JSON-P {@link JsonObject}. + */ +class JavaxJsonObjectAdapter implements JsonObjectAdapter { + + private final JsonObject delegate; + + JavaxJsonObjectAdapter(JsonObject delegate) { + this.delegate = delegate; + } + + @Override + public int length() { + return delegate.size(); + } + + @Override + public String[] keys() { + return delegate.keySet().toArray(new String[0]); + } + + @Override + public boolean has(String key) { + return delegate.containsKey(key); + } + + @Override + public JsonValue get(String key) { + return delegate.get(key); + } + + @Override + public void put(String key, JsonValue value) { + throw new UnsupportedOperationException(); + } + + @Override + public Map toMap() { + return Collections.unmodifiableMap(delegate); + } + + @Override + public JsonValue unwrap() { + return delegate; + } + +} diff --git a/core/src/main/java/org/everit/json/schema/javax/json/Jsr353Adaptation.java b/core/src/main/java/org/everit/json/schema/javax/json/Jsr353Adaptation.java new file mode 100644 index 000000000..734b5e7fc --- /dev/null +++ b/core/src/main/java/org/everit/json/schema/javax/json/Jsr353Adaptation.java @@ -0,0 +1,53 @@ +package org.everit.json.schema.javax.json; + +import javax.json.Json; +import javax.json.JsonValue; +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * An adaptation for the JSON types of JSR-353 for support of Java EE 7. + *

+ * In JSR-353, none of the {@link Json} methods to create JSON scalar + * values are available. However there are methods to create array + * and object builders. To create a single scalar value, in this + * implementation we use an array builder; adding an intrinsic scalar + * value and then extracting it from the completed array. + *

+ * While this approach has some additional overhead, it at least provides + * a means to support Java EE 7 which includes the JSR-353 specification + * of the standard JSON types. + */ +public class Jsr353Adaptation extends JavaxJsonAdaptation { + + @Override + JsonValue createValue(String value) { + return Json.createArrayBuilder().add(value).build().get(0); + } + + @Override + JsonValue createValue(int value) { + return Json.createArrayBuilder().add(value).build().get(0); + } + + @Override + JsonValue createValue(double value) { + return Json.createArrayBuilder().add(value).build().get(0); + } + + @Override + JsonValue createValue(long value) { + return Json.createArrayBuilder().add(value).build().get(0); + } + + @Override + JsonValue createValue(BigInteger value) { + return Json.createArrayBuilder().add(value).build().get(0); + } + + @Override + JsonValue createValue(BigDecimal value) { + return Json.createArrayBuilder().add(value).build().get(0); + } + +} diff --git a/core/src/main/java/org/everit/json/schema/javax/json/Jsr374Adaptation.java b/core/src/main/java/org/everit/json/schema/javax/json/Jsr374Adaptation.java new file mode 100644 index 000000000..8ca90c2d6 --- /dev/null +++ b/core/src/main/java/org/everit/json/schema/javax/json/Jsr374Adaptation.java @@ -0,0 +1,43 @@ +package org.everit.json.schema.javax.json; + +import javax.json.Json; +import javax.json.JsonValue; +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * An adaptation for the JSON types of JSR-374 for support of Java EE 8. + */ +public class Jsr374Adaptation extends JavaxJsonAdaptation { + + @Override + JsonValue createValue(String value) { + return Json.createValue(value); + } + + @Override + JsonValue createValue(int value) { + return Json.createValue(value); + } + + @Override + JsonValue createValue(double value) { + return Json.createValue(value); + } + + @Override + JsonValue createValue(long value) { + return Json.createValue(value); + } + + @Override + JsonValue createValue(BigInteger value) { + return Json.createValue(value); + } + + @Override + JsonValue createValue(BigDecimal value) { + return Json.createValue(value); + } + +} diff --git a/core/src/main/java/org/everit/json/schema/javax/json/package-info.java b/core/src/main/java/org/everit/json/schema/javax/json/package-info.java new file mode 100644 index 000000000..8f9b263fb --- /dev/null +++ b/core/src/main/java/org/everit/json/schema/javax/json/package-info.java @@ -0,0 +1,4 @@ +/** + * Adaptation for the JSON-P types of {@code javax.json} + */ +package org.everit.json.schema.javax.json; \ No newline at end of file diff --git a/core/src/main/java/org/everit/json/schema/spi/JsonAdaptation.java b/core/src/main/java/org/everit/json/schema/spi/JsonAdaptation.java new file mode 100644 index 000000000..8ab537a11 --- /dev/null +++ b/core/src/main/java/org/everit/json/schema/spi/JsonAdaptation.java @@ -0,0 +1,82 @@ +package org.everit.json.schema.spi; + +/** + * A service that adapts JSON types for use in schema validation. + *

+ * An adaption is responsible for adapting implementation-specific JSON object and array + * structures to instances of the {@link JsonObjectAdapter} and {@link JsonArrayAdapter} types. + * JSON scalar values are adapted to an intrinsic type (String, Boolean, Number). + *

+ * An adaptation can assume that for a given validation operation, exactly one + * adaptation is in use, and that any given {@link JsonAdapter} was produced by + * the single adaptation that is in use. + * + * @param the base type common to the types used in the underlying JSON implementation + */ +public interface JsonAdaptation { + + /** + * Gets the implementation-specific type that represents a JSON array. + * @return array type + */ + Class arrayType(); + + /** + * Gets the implementation-specific type that represents a JSON object. + * @return object type + */ + Class objectType(); + + /** + * Gets the types supported by this implementation. + * @return array of supported types + */ + Class[] supportedTypes(); + + /** + * Given an arbitrary type, tests whether the type is recognized as an adaptable type + * in this adaptation. + *

+ * An implementation does not need to recognize the intrinsic types (String, Boolean, Number) + * but must recognize its own types. + * + * @param type the subject type to test + * @return {@code true} if {@code type} is an adaptable type + */ + boolean isSupportedType(Class type); + + /** + * Given an arbitrary value, tests whether the value is logically equal to null in this + * adaptation. + * @param value the value to test. + * @return {@code true} if {@code value == null} or if {@code value} is equal to the JSON + * null representation supported by this adaptation (if any) + */ + boolean isNull(Object value); + + /** + * Adapts the given value by applying an adaptation function based on the input value. + * @param value the subject value to adapt + * @return the adapted value which must be either + * (1) an intrinsic representation of an implementation-specific JSON scalar value + * (e.g. String, Boolean, Number) + * OR (2) a {@link JsonArrayAdapter} for an implementation-specific JSON array + * OR (3) a {@link JsonObjectAdapter} for an implementation-specific JSON object + * OR (4) the input {@code value} if the value is not a recognized + * implementation-specific type + */ + Object adapt(Object value); + + /** + * Inverts the adaptation function applied to the given value. + * @param value the subject adapted value + * @return the result of inverting any recognized adaptation in {@code value} OR + * {@code value} if no adaptation was recognized. If {@code value} is an instance of + * {@link JsonAdapter} the return value is generally the result of + * {@link JsonAdapter#unwrap()}. If {@code value} has an intrinsic type (String, + * Boolean, Number), the return value may be an implementation-specific representation + * of any of these types. + */ + T invert(Object value); + +} diff --git a/core/src/main/java/org/everit/json/schema/spi/JsonAdapter.java b/core/src/main/java/org/everit/json/schema/spi/JsonAdapter.java new file mode 100644 index 000000000..06f883c59 --- /dev/null +++ b/core/src/main/java/org/everit/json/schema/spi/JsonAdapter.java @@ -0,0 +1,17 @@ +package org.everit.json.schema.spi; + +/** + * A adapter for a JSON structure type (array or object). + * + * @param the base type common to the types used in the underlying JSON implementation + */ +public interface JsonAdapter { + + /** + * Gets the implementation-specific representation of the structure instance. + * @return implementation-specific JSON structure representation; generally this is the + * delegate of the adapter + */ + T unwrap(); + +} diff --git a/core/src/main/java/org/everit/json/schema/spi/JsonArrayAdapter.java b/core/src/main/java/org/everit/json/schema/spi/JsonArrayAdapter.java new file mode 100644 index 000000000..aca8c157a --- /dev/null +++ b/core/src/main/java/org/everit/json/schema/spi/JsonArrayAdapter.java @@ -0,0 +1,56 @@ +package org.everit.json.schema.spi; + +import java.util.List; + +/** + * An adapter for a JSON array. + *

+ * This interface represents the contract between a provider's JSON array implementation + * and a validator utilizing JSON Schema. + */ +public interface JsonArrayAdapter extends JsonAdapter { + + /** + * Gets the length of the array. + * @return the number of elements in the array; i.e. the maximum value for the array + * index which can be used {@link #get(int)} to retrieve a value or with + * {@link #put(int, Object)} to store a value + */ + int length(); + + /** + * Retrieves the element of the array at the given index. + *

+ * The validator has no need to retrieve values beyond the bounds of the underlying + * array. Therefore it is reasonable and expected that an implementation throw an + * unchecked exception when {@code index} is outside of the bounds of the array. + * However, it is not a requirement that an exception be thrown in this circumstance. + * + * @param index the index of the element to retrieve + * @return the value of the element or {@code null} if no element + * exists at the specified index + */ + T get(int index); + + /** + * Stores an the element in the array at the given index replacing any existing element + * at the same index. Adaptation from intrinsic types or {@link JsonAdapter} subtypes + * is performed as needed on the input value. + *

+ * The validator has no need to store values beyond the bounds of the underlying + * array. Therefore it is reasonable and expected that an implementation throw an + * unchecked exception when {@code index} is outside of the bounds of the array. + * However, it is not a requirement that an exception be thrown in this circumstance. + * + * @param index index at which to store the element + * @param value the value to store + */ + void put(int index, T value); + + /** + * Adapts this array to the {@link List} interface. + * @return list adaptation + */ + List toList(); + +} diff --git a/core/src/main/java/org/everit/json/schema/spi/JsonObjectAdapter.java b/core/src/main/java/org/everit/json/schema/spi/JsonObjectAdapter.java new file mode 100644 index 000000000..9ef6ac445 --- /dev/null +++ b/core/src/main/java/org/everit/json/schema/spi/JsonObjectAdapter.java @@ -0,0 +1,61 @@ +package org.everit.json.schema.spi; + +import java.util.Map; + +/** + * An adapter for a JSON object. + *

+ * This interface represents the contract between a provider's JSON object implementation + * and a validator utilizing JSON Schema. + */ +public interface JsonObjectAdapter extends JsonAdapter { + + /** + * Gets the length of the object. + * @return the number of key-value pairs contained in the object + */ + int length(); + + /** + * Gets an array containing the keys for the key-value pairs contained in the object. + * @return array of keys for values that may be retrieved using {@link #get(String)). + */ + String[] keys(); + + /** + * Tests whether a given key has a corresponding value in the object. + * @param key the subject key + * @return {@code true} if this object has a value that corresponds to {@code key} + */ + boolean has(String key); + + /** + * Retrieves the value associated with a given key from this object, applying adaptation + * to implementation specific types as needed. + *

+ * It is expected that the validator will always test for the presence of a key via + * the {@link #has(String)} method, before attempting to retrieve it. Therefore it is + * reasonable and expected that an implementation throw an unchecked exception when + * {@code key} does not exist in object. However, it is not a requirement that an + * exception be thrown in this circumstance. + * + * @param key the subject key + * @return the value of the element or {@code null} + */ + T get(String key); + + /** + * Replaces any existing value associated with a given key in this object. + * + * @param key the subject key + * @param value the value to store + */ + void put(String key, T value); + + /** + * Adapts the object to the {@link Map} interface. + * @return map adaptation + */ + Map toMap(); + +} diff --git a/core/src/main/java/org/everit/json/schema/spi/package-info.java b/core/src/main/java/org/everit/json/schema/spi/package-info.java new file mode 100644 index 000000000..0e84ef64f --- /dev/null +++ b/core/src/main/java/org/everit/json/schema/spi/package-info.java @@ -0,0 +1,9 @@ +/** + * JSON type service provider interface + *

+ * This package specifies an interface to be implemented for a particular JSON type + * system to make it compatible with the JSON validation components. An implementation + * of this SPI allows validation to performed on JSON objects represented in an + * arbitrary type system. + */ +package org.everit.json.schema.spi; \ No newline at end of file diff --git a/core/src/test/java/org/everit/json/schema/JSONAdaptationTest.java b/core/src/test/java/org/everit/json/schema/JSONAdaptationTest.java new file mode 100644 index 000000000..0b22668db --- /dev/null +++ b/core/src/test/java/org/everit/json/schema/JSONAdaptationTest.java @@ -0,0 +1,98 @@ +package org.everit.json.schema; + +import org.everit.json.schema.spi.JsonAdapter; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +public class JSONAdaptationTest { + + private final JSONAdaptation adaptation = new JSONAdaptation(); + + @Test + public void testArrayType() { + assertEquals(JSONArray.class, adaptation.arrayType()); + } + + @Test + public void testObjectType() { + assertEquals(JSONObject.class, adaptation.objectType()); + } + + @Test + public void testIsSupportedType() { + assertTrue(adaptation.isSupportedType(JSONArray.class)); + assertTrue(adaptation.isSupportedType(JSONObject.class)); + assertTrue(adaptation.isSupportedType(JSONObject.NULL.getClass())); + } + + @Test + public void testSupportedTypes() { + final List> types = Arrays.asList(adaptation.supportedTypes()); + assertEquals(3, types.size()); + assertTrue(types.contains(JSONArray.class)); + assertTrue(types.contains(JSONObject.class)); + assertTrue(types.contains(JSONObject.NULL.getClass())); + } + + @Test + public void testAdaptIntrinsics() { + assertEquals("value", adaptation.adapt("value")); + assertEquals(true, adaptation.adapt(true)); + assertEquals(1, adaptation.adapt(1)); + assertNull(adaptation.adapt(null)); + } + + @Test + public void testAdaptAdapter() { + final JsonAdapter adapter = () -> null; + assertSame(adapter, adaptation.adapt(adapter)); + } + + @Test + public void testAdaptJSONNull() { + assertNull(adaptation.adapt(JSONObject.NULL)); + } + + @Test + public void testAdaptJSONObject() { + final JSONObject object = new JSONObject(); + final Object result = adaptation.adapt(object); + assertTrue(result instanceof JSONObjectAdapter); + assertSame(object, ((JSONObjectAdapter) result).unwrap()); + } + + @Test + public void testAdaptJSONArray() { + final JSONArray array = new JSONArray(); + final Object result = adaptation.adapt(array); + assertTrue(result instanceof JSONArrayAdapter); + assertSame(array, ((JSONArrayAdapter) result).unwrap()); + } + + @Test + public void testInvertIntrinsics() { + assertEquals("value", adaptation.invert("value")); + assertEquals(true, adaptation.invert(true)); + assertEquals(1, adaptation.invert(1)); + assertEquals(JSONObject.NULL, adaptation.invert(null)); + } + + @Test + public void testInvertObjectAdapter() { + final JSONObject object = new JSONObject(); + assertSame(object, adaptation.invert(new JSONObjectAdapter(object))); + } + + @Test + public void testInvertArrayAdapter() { + final JSONArray array = new JSONArray(); + assertSame(array, adaptation.invert(new JSONArrayAdapter(array))); + } + +} diff --git a/core/src/test/java/org/everit/json/schema/JSONArrayAdapterTest.java b/core/src/test/java/org/everit/json/schema/JSONArrayAdapterTest.java new file mode 100644 index 000000000..eb82907fb --- /dev/null +++ b/core/src/test/java/org/everit/json/schema/JSONArrayAdapterTest.java @@ -0,0 +1,23 @@ +package org.everit.json.schema; + +import org.json.JSONArray; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class JSONArrayAdapterTest { + + @Test + public void testAdapter() { + final JSONArrayAdapter adapter = new JSONArrayAdapter( + new JSONArray().put("value")); + + assertEquals(1, adapter.length()); + assertEquals("value", adapter.get(0)); + assertEquals("value", adapter.toList().get(0)); + + adapter.put(0, "otherValue"); + assertEquals("otherValue", adapter.get(0)); + } + +} diff --git a/core/src/test/java/org/everit/json/schema/JSONObjectAdapterTest.java b/core/src/test/java/org/everit/json/schema/JSONObjectAdapterTest.java new file mode 100644 index 000000000..3e2f4efdd --- /dev/null +++ b/core/src/test/java/org/everit/json/schema/JSONObjectAdapterTest.java @@ -0,0 +1,47 @@ +/* + * File created on Sep 13, 2018 + * + * Copyright (c) 2018 Carl Harris, Jr + * and others as noted + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.everit.json.schema; + +import org.json.JSONObject; +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class JSONObjectAdapterTest { + + @Test + public void testAdapter() { + final JSONObjectAdapter adapter = new JSONObjectAdapter( + new JSONObject().put("key", "value")); + + assertEquals(1, adapter.length()); + assertTrue(adapter.has("key")); + assertEquals("value", adapter.get("key")); + assertTrue(Arrays.asList(adapter.keys()).contains("key")); + assertTrue(adapter.toMap().containsKey("key")); + assertEquals("value", adapter.toMap().get("key")); + + adapter.put("key", "otherValue"); + assertEquals("otherValue", adapter.get("key")); + } + +} diff --git a/core/src/test/java/org/everit/json/schema/JavaxJsonValidatingVisitorTest.java b/core/src/test/java/org/everit/json/schema/JavaxJsonValidatingVisitorTest.java new file mode 100644 index 000000000..3d21ae0ad --- /dev/null +++ b/core/src/test/java/org/everit/json/schema/JavaxJsonValidatingVisitorTest.java @@ -0,0 +1,154 @@ +/* + * File created on Sep 12, 2018 + * + * Copyright (c) 2018 Carl Harris, Jr + * and others as noted + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.everit.json.schema; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.everit.json.schema.javax.json.Jsr353Adaptation; +import org.everit.json.schema.javax.json.Jsr374Adaptation; +import org.everit.json.schema.spi.JsonAdaptation; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonValue; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.*; + +@RunWith(JUnitParamsRunner.class) +public class JavaxJsonValidatingVisitorTest { + + private static final Jsr374Adaptation JSR_374_ADAPTATION = new Jsr374Adaptation(); + private static final Jsr353Adaptation JSR_353_ADAPTATION = new Jsr353Adaptation(); + + private ValidationFailureReporter reporter; + + @Before + public void before() { + reporter = mock(ValidationFailureReporter.class); + } + + public Object[] javaxJsonAdaptations() { + return new Object[] { JSR_374_ADAPTATION, JSR_353_ADAPTATION }; + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void passesTypeCheck_otherType_noRequires(JsonAdaptation jsonAdaptation) { + ValidatingVisitor subject = new ValidatingVisitor("string", reporter, null, jsonAdaptation); + assertFalse(subject.passesTypeCheck(jsonAdaptation.objectType(), false, null)); + verifyZeroInteractions(reporter); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void passesTypeCheck_otherType_requires(JsonAdaptation jsonAdaptation) { + ValidatingVisitor subject = new ValidatingVisitor("string", reporter, null, jsonAdaptation); + assertFalse(subject.passesTypeCheck(jsonAdaptation.objectType(), true, null)); + verify(reporter).failure(JsonObject.class, "string"); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void passesTypeCheck_otherType_nullPermitted_nullObject(JsonAdaptation jsonAdaptation) { + ValidatingVisitor subject = new ValidatingVisitor(JsonValue.NULL, reporter, null, jsonAdaptation); + assertFalse(subject.passesTypeCheck(jsonAdaptation.objectType(), true, Boolean.TRUE)); + verifyZeroInteractions(reporter); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void passesTypeCheck_otherType_nullPermitted_nullReference(JsonAdaptation jsonAdaptation) { + ValidatingVisitor subject = new ValidatingVisitor(null, reporter, null, jsonAdaptation); + assertFalse(subject.passesTypeCheck(jsonAdaptation.objectType(), true, Boolean.TRUE)); + verifyZeroInteractions(reporter); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void passesTypeCheck_nullPermitted_nonNullValue(JsonAdaptation jsonAdaptation) { + ValidatingVisitor subject = new ValidatingVisitor("string", reporter, null, jsonAdaptation); + assertFalse(subject.passesTypeCheck(jsonAdaptation.objectType(), true, Boolean.TRUE)); + verify(reporter).failure(jsonAdaptation.objectType(), "string"); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void passesTypeCheck_requiresType_nullableIsNull(JsonAdaptation jsonAdaptation) { + ValidatingVisitor subject = new ValidatingVisitor(null, reporter, null, jsonAdaptation); + assertFalse(subject.passesTypeCheck(jsonAdaptation.objectType(), true, null)); + verify(reporter).failure(jsonAdaptation.objectType(), null); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void passesTypeCheck_sameType(JsonAdaptation jsonAdaptation) { + ValidatingVisitor subject = new ValidatingVisitor("string", reporter, null, jsonAdaptation); + assertTrue(subject.passesTypeCheck(String.class, true, Boolean.TRUE)); + verifyZeroInteractions(reporter); + } + + public Object[] permittedTypes() { + return new Object[] { + new Object[] { "str" }, + new Object[] { 1 }, + new Object[] { 1L }, + new Object[] { 1.0 }, + new Object[] { 1.0f }, + new Object[] { new BigInteger("42") }, + new Object[] { new BigDecimal("42.3") }, + new Object[] { true }, + new Object[] { null }, + new Object[] { JsonValue.NULL }, + new Object[] { JsonValue.FALSE }, + new Object[] { JsonValue.TRUE }, + new Object[] { Json.createValue("str") }, + new Object[] { Json.createValue(1) }, + new Object[] { Json.createObjectBuilder().build() }, + new Object[] { Json.createArrayBuilder().build() }, + }; + } + + public Object[] notPermittedTypes() { + return new Object[] { + new Object[] { new ArrayList() }, + new Object[] { new RuntimeException() } + }; + } + + @Test + @Parameters(method = "permittedTypes") + public void permittedTypeSuccess(Object subject) { + new ValidatingVisitor(subject, reporter, ReadWriteValidator.NONE, JSR_374_ADAPTATION); + } + + @Test(expected = IllegalArgumentException.class) + @Parameters(method = "notPermittedTypes") + public void notPermittedTypeFailure(Object subject) { + new ValidatingVisitor(subject, reporter, ReadWriteValidator.NONE, JSR_374_ADAPTATION); + } + +} diff --git a/core/src/test/java/org/everit/json/schema/ObjectComparatorTest.java b/core/src/test/java/org/everit/json/schema/ObjectComparatorTest.java index 8784152e8..513c3a5b3 100644 --- a/core/src/test/java/org/everit/json/schema/ObjectComparatorTest.java +++ b/core/src/test/java/org/everit/json/schema/ObjectComparatorTest.java @@ -2,6 +2,8 @@ import static org.junit.Assert.assertFalse; +import org.everit.json.schema.spi.JsonArrayAdapter; +import org.everit.json.schema.spi.JsonObjectAdapter; import org.json.JSONArray; import org.json.JSONObject; import org.junit.Test; @@ -17,6 +19,12 @@ public class ObjectComparatorTest { public static final JSONArray EMPTY_ARRAY = new JSONArray(); public static final JSONObject EMPTY_OBJECT = new JSONObject(); + private static final JsonArrayAdapter EMPTY_ARRAY_ADAPTER = + new JSONArrayAdapter(new JSONArray()); + + private static final JsonObjectAdapter EMPTY_OBJECT_ADAPTER = + new JSONObjectAdapter(new JSONObject()); + private Object[][] failingCases() { return new Object[][] { { "array, null", EMPTY_ARRAY, null }, @@ -24,7 +32,17 @@ private Object[][] failingCases() { { "object, null", EMPTY_OBJECT, null }, { "arrays with different length", EMPTY_ARRAY, new JSONArray("[null]") }, { "arrays with different elems", new JSONArray("[true, false]"), new JSONArray("[false, true]") }, - { "objects with different length", EMPTY_OBJECT, new JSONObject("{\"a\":true}") } + { "objects with different length", EMPTY_OBJECT, new JSONObject("{\"a\":true}") }, + { "array adapter, null", EMPTY_ARRAY_ADAPTER, null }, + { "array adapter, object adapter", EMPTY_ARRAY_ADAPTER, EMPTY_OBJECT_ADAPTER }, + { "object adapter, null", EMPTY_OBJECT_ADAPTER, null }, + { "array adapters with different length", EMPTY_ARRAY_ADAPTER, new JSONArrayAdapter(new JSONArray("[null]")) }, + { "array adapters with different elems", new JSONArrayAdapter(new JSONArray("[true, false]")), new JSONArrayAdapter(new JSONArray("[false, true]")) }, + { "object adapters with different length", EMPTY_OBJECT_ADAPTER, new JSONObjectAdapter(new JSONObject("{\"a\":true}")) }, + { "array, array adapter", EMPTY_ARRAY, EMPTY_ARRAY_ADAPTER }, + { "object, object adapter", EMPTY_OBJECT, EMPTY_OBJECT_ADAPTER }, + { "array, object adapter", EMPTY_ARRAY, EMPTY_OBJECT_ADAPTER }, + { "object, array adapter", EMPTY_OBJECT, EMPTY_ARRAY_ADAPTER }, }; } diff --git a/core/src/test/java/org/everit/json/schema/ValidatingVisitorTest.java b/core/src/test/java/org/everit/json/schema/ValidatingVisitorTest.java index 7d809197c..bd2fd626a 100644 --- a/core/src/test/java/org/everit/json/schema/ValidatingVisitorTest.java +++ b/core/src/test/java/org/everit/json/schema/ValidatingVisitorTest.java @@ -22,6 +22,8 @@ @RunWith(JUnitParamsRunner.class) public class ValidatingVisitorTest { + private static final JSONAdaptation ORG_JSON_ADAPTATION = new JSONAdaptation(); + private ValidationFailureReporter reporter; @Before @@ -31,49 +33,49 @@ public void before() { @Test public void passesTypeCheck_otherType_noRequires() { - ValidatingVisitor subject = new ValidatingVisitor("string", reporter, null); + ValidatingVisitor subject = new ValidatingVisitor("string", reporter, null, ORG_JSON_ADAPTATION); assertFalse(subject.passesTypeCheck(JSONObject.class, false, null)); verifyZeroInteractions(reporter); } @Test public void passesTypeCheck_otherType_requires() { - ValidatingVisitor subject = new ValidatingVisitor("string", reporter, null); + ValidatingVisitor subject = new ValidatingVisitor("string", reporter, null, ORG_JSON_ADAPTATION); assertFalse(subject.passesTypeCheck(JSONObject.class, true, null)); verify(reporter).failure(JSONObject.class, "string"); } @Test public void passesTypeCheck_otherType_nullPermitted_nullObject() { - ValidatingVisitor subject = new ValidatingVisitor(JSONObject.NULL, reporter, null); + ValidatingVisitor subject = new ValidatingVisitor(JSONObject.NULL, reporter, null, ORG_JSON_ADAPTATION); assertFalse(subject.passesTypeCheck(JSONObject.class, true, Boolean.TRUE)); verifyZeroInteractions(reporter); } @Test public void passesTypeCheck_otherType_nullPermitted_nullReference() { - ValidatingVisitor subject = new ValidatingVisitor(null, reporter, null); + ValidatingVisitor subject = new ValidatingVisitor(null, reporter, null, ORG_JSON_ADAPTATION); assertFalse(subject.passesTypeCheck(JSONObject.class, true, Boolean.TRUE)); verifyZeroInteractions(reporter); } @Test public void passesTypeCheck_nullPermitted_nonNullValue() { - ValidatingVisitor subject = new ValidatingVisitor("string", reporter, null); + ValidatingVisitor subject = new ValidatingVisitor("string", reporter, null, ORG_JSON_ADAPTATION); assertFalse(subject.passesTypeCheck(JSONObject.class, true, Boolean.TRUE)); verify(reporter).failure(JSONObject.class, "string"); } @Test public void passesTypeCheck_requiresType_nullableIsNull() { - ValidatingVisitor subject = new ValidatingVisitor(null, reporter, null); + ValidatingVisitor subject = new ValidatingVisitor(null, reporter, null, ORG_JSON_ADAPTATION); assertFalse(subject.passesTypeCheck(JSONObject.class, true, null)); verify(reporter).failure(JSONObject.class, null); } @Test public void passesTypeCheck_sameType() { - ValidatingVisitor subject = new ValidatingVisitor("string", reporter, null); + ValidatingVisitor subject = new ValidatingVisitor("string", reporter, null, ORG_JSON_ADAPTATION); assertTrue(subject.passesTypeCheck(String.class, true, Boolean.TRUE)); verifyZeroInteractions(reporter); } @@ -105,13 +107,13 @@ public Object[] notPermittedTypes() { @Test @Parameters(method = "permittedTypes") public void permittedTypeSuccess(Object subject) { - new ValidatingVisitor(subject, reporter, ReadWriteValidator.NONE); + new ValidatingVisitor(subject, reporter, ReadWriteValidator.NONE, ORG_JSON_ADAPTATION); } @Test(expected = IllegalArgumentException.class) @Parameters(method = "notPermittedTypes") public void notPermittedTypeFailure(Object subject) { - new ValidatingVisitor(subject, reporter, ReadWriteValidator.NONE); + new ValidatingVisitor(subject, reporter, ReadWriteValidator.NONE, ORG_JSON_ADAPTATION); } } diff --git a/core/src/test/java/org/everit/json/schema/javax/json/JavaxJsonArrayAdapterTest.java b/core/src/test/java/org/everit/json/schema/javax/json/JavaxJsonArrayAdapterTest.java new file mode 100644 index 000000000..ebae31c74 --- /dev/null +++ b/core/src/test/java/org/everit/json/schema/javax/json/JavaxJsonArrayAdapterTest.java @@ -0,0 +1,45 @@ +/* + * File created on Sep 14, 2018 + * + * Copyright (c) 2018 Carl Harris, Jr + * and others as noted + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.everit.json.schema.javax.json; + +import org.junit.Test; + +import javax.json.Json; + +import static org.junit.Assert.assertEquals; + +public class JavaxJsonArrayAdapterTest { + + private final JavaxJsonArrayAdapter adapter = new JavaxJsonArrayAdapter( + Json.createArrayBuilder().add("value").build()); + + @Test + public void testAdapter() { + + assertEquals(1, adapter.length()); + assertEquals(Json.createValue("value"), adapter.get(0)); + assertEquals(Json.createValue("value"), adapter.toList().get(0)); + } + + @Test(expected = UnsupportedOperationException.class) + public void testAdapterPut() { + adapter.put(0, Json.createValue("value")); + } + +} diff --git a/core/src/test/java/org/everit/json/schema/javax/json/JavaxJsonObjectAdapterTest.java b/core/src/test/java/org/everit/json/schema/javax/json/JavaxJsonObjectAdapterTest.java new file mode 100644 index 000000000..1969891ad --- /dev/null +++ b/core/src/test/java/org/everit/json/schema/javax/json/JavaxJsonObjectAdapterTest.java @@ -0,0 +1,50 @@ +/* + * File created on Sep 14, 2018 + * + * Copyright (c) 2018 Carl Harris, Jr + * and others as noted + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.everit.json.schema.javax.json; + +import org.junit.Test; + +import javax.json.Json; + +import java.util.Arrays; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class JavaxJsonObjectAdapterTest { + + private final JavaxJsonObjectAdapter adapter = new JavaxJsonObjectAdapter( + Json.createObjectBuilder().add("key", "value").build()); + + @Test + public void testAdapter() { + assertEquals(1, adapter.length()); + assertTrue(adapter.has("key")); + assertEquals(Json.createValue("value"), adapter.get("key")); + assertTrue(Arrays.asList(adapter.keys()).contains("key")); + assertEquals(Json.createValue("value"), adapter.toMap().get("key")); + } + + @Test(expected = UnsupportedOperationException.class) + public void testAdapterPut() { + adapter.put("key", Json.createValue("value")); + } + +} diff --git a/core/src/test/java/org/everit/json/schema/javax/json/JavaxJsonValidationTest.java b/core/src/test/java/org/everit/json/schema/javax/json/JavaxJsonValidationTest.java new file mode 100644 index 000000000..4758a28ea --- /dev/null +++ b/core/src/test/java/org/everit/json/schema/javax/json/JavaxJsonValidationTest.java @@ -0,0 +1,170 @@ +package org.everit.json.schema.javax.json; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.everit.json.schema.spi.JsonAdapter; +import org.everit.json.schema.spi.JsonArrayAdapter; +import org.everit.json.schema.spi.JsonObjectAdapter; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.json.*; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +@RunWith(JUnitParamsRunner.class) +public class JavaxJsonValidationTest { + + private static final Jsr374Adaptation JSR_374_ADAPTATION = new Jsr374Adaptation(); + private static final Jsr353Adaptation JSR_353_ADAPTATION = new Jsr353Adaptation(); + + public Object[] javaxJsonAdaptations() { + return new Object[] { JSR_374_ADAPTATION, JSR_353_ADAPTATION }; + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void testArrayType(JavaxJsonAdaptation adaptation) { + assertEquals(JsonArray.class, adaptation.arrayType()); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void testObjectType(JavaxJsonAdaptation adaptation) { + assertEquals(JsonObject.class, adaptation.objectType()); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void testIsSupportedType(JavaxJsonAdaptation adaptation) { + assertTrue(adaptation.isSupportedType(JsonValue.class)); + assertTrue(adaptation.isSupportedType(JsonNumber.class)); + assertTrue(adaptation.isSupportedType(JsonString.class)); + assertTrue(adaptation.isSupportedType(JsonObject.class)); + assertTrue(adaptation.isSupportedType(JsonArray.class)); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void testSupportedTypes(JavaxJsonAdaptation adaptation) { + final List> types = Arrays.asList(adaptation.supportedTypes()); + assertEquals(1, types.size()); + assertTrue(types.contains(JsonValue.class)); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void testAdaptIntrinsics(JavaxJsonAdaptation adaptation) { + assertEquals("value", adaptation.adapt("value")); + assertEquals(true, adaptation.adapt(true)); + assertEquals(1, adaptation.adapt(1)); + assertNull(adaptation.adapt(null)); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void testAdaptAdapter(JavaxJsonAdaptation adaptation) { + final JsonAdapter adapter = () -> null; + assertSame(adapter, adaptation.adapt(adapter)); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void testAdaptJsonScalars(JavaxJsonAdaptation adaptation) { + assertNull(adaptation.adapt(JsonValue.NULL)); + assertEquals(true, adaptation.adapt(JsonValue.TRUE)); + assertEquals(false, adaptation.adapt(JsonValue.FALSE)); + assertEquals("value", adaptation.adapt(Json.createValue("value"))); + assertEquals(BigInteger.ONE, adaptation.adapt(Json.createValue(1))); + assertEquals(BigInteger.ONE, adaptation.adapt(Json.createValue(1L))); + assertEquals(BigInteger.ONE, adaptation.adapt(Json.createValue(BigInteger.ONE))); + assertEquals(BigDecimal.valueOf(1.1), adaptation.adapt(Json.createValue(BigDecimal.valueOf(1.1)))); + assertEquals(BigDecimal.valueOf(1.1), adaptation.adapt(Json.createValue(1.1))); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void testAdaptJsonObject(JavaxJsonAdaptation adaptation) { + final JsonObject object = Json.createObjectBuilder().build(); + final Object result = adaptation.adapt(object); + assertTrue(result instanceof JsonObjectAdapter); + assertSame(object, ((JsonObjectAdapter) result).unwrap()); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void testAdaptJsonArray(JavaxJsonAdaptation adaptation) { + final JsonArray object = Json.createArrayBuilder().build(); + final Object result = adaptation.adapt(object); + assertTrue(result instanceof JsonArrayAdapter); + assertSame(object, ((JsonArrayAdapter) result).unwrap()); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void testInvertIntrinsics(JavaxJsonAdaptation adaptation) { + assertEquals(JsonValue.NULL, adaptation.invert(null)); + assertEquals(JsonValue.TRUE, adaptation.invert(true)); + assertEquals(JsonValue.FALSE, adaptation.invert(false)); + assertEquals(Json.createValue("value"), adaptation.invert("value")); + assertEquals(Json.createValue(1), adaptation.invert(1)); + assertEquals(Json.createValue(1L), adaptation.invert(1L)); + assertEquals(Json.createValue(1.0), adaptation.invert(1.0)); + assertEquals(Json.createValue(BigInteger.ONE), adaptation.invert(BigInteger.ONE)); + assertEquals(Json.createValue(BigDecimal.ONE), adaptation.invert(BigDecimal.ONE)); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void testInvertObjectAdapter(JavaxJsonAdaptation adaptation) { + final JsonObject object = Json.createObjectBuilder().build(); + assertSame(object, adaptation.invert(new JavaxJsonObjectAdapter(object))); + } + + @Test + @Parameters(method = "javaxJsonAdaptations") + public void testInvertArrayAdapter(JavaxJsonAdaptation adaptation) { + final JsonArray array = Json.createArrayBuilder().build(); + assertSame(array, adaptation.invert(new JavaxJsonArrayAdapter(array))); + } + + @Test + public void testNewInstanceDefaultClassLoader() { + final ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(null); + assertTrue(JavaxJsonAdaptation.newInstance() instanceof Jsr374Adaptation); + Thread.currentThread().setContextClassLoader(tccl); + } + + @Test + public void testNewInstanceThreadContextClassLoader() { + final ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + assertTrue(JavaxJsonAdaptation.newInstance() instanceof Jsr374Adaptation); + Thread.currentThread().setContextClassLoader(tccl); + } + + @Test + public void testNewInstanceWithClassLoader() { + assertTrue(JavaxJsonAdaptation.newInstance(getClass().getClassLoader()) + instanceof Jsr374Adaptation); + } + + @Test + public void testDetermineProvider() { + assertEquals(JavaxJsonAdaptation.JSR_374_ADAPTATION, + JavaxJsonAdaptation.determineProvider("createValue", String.class)); + assertEquals(JavaxJsonAdaptation.JSR_353_ADAPTATION, + JavaxJsonAdaptation.determineProvider(".noSuchMethod")); + } + + @Test(expected = RuntimeException.class) + public void testInstanceWhenCannotInstantiate() { + JavaxJsonAdaptation.newInstance(getClass().getClassLoader(), ".noSuchProviderName"); + } + +}