Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor JSON API into own Module #229

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,13 @@
</plugins>
</build>
<dependencies>
<!-- TODO: Move into own artifact-->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180130</version>
</dependency>
<!-- -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class ArraySchemaValidatingVisitor extends Visitor {

private final ValidatingVisitor owner;

// TODO: New API (JsonArray)
private JSONArray arraySubject;

private ArraySchema arraySchema;
Expand All @@ -30,6 +31,7 @@ public ArraySchemaValidatingVisitor(Object subject, ValidatingVisitor owner) {
}

@Override void visitArraySchema(ArraySchema arraySchema) {
// TODO: New API (JsonArray)
if (owner.passesTypeCheck(JSONArray.class, arraySchema.requiresArray(), arraySchema.isNullable())) {
this.arraySubject = (JSONArray) subject;
this.subjectLength = arraySubject.length();
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/java/org/everit/json/schema/EnumSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@
public class EnumSchema extends Schema {

static Object toJavaValue(Object orig) {
// TODO: New API (JsonArray)
if (orig instanceof JSONArray) {
return ((JSONArray) orig).toList();
// TODO: New API (JsonObject)
} else if (orig instanceof JSONObject) {
return ((JSONObject) orig).toMap();
// TODO: New API (Null)
} else if (orig == JSONObject.NULL) {
return null;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ void describePropertiesTo(JSONPrinter writer) {
try {
writer.ifPresent("exclusiveMinimum", exclusiveMinimumLimit);
writer.ifPresent("exclusiveMaximum", exclusiveMaximumLimit);
} catch (JSONException e) {
} catch (JSONException e) { // TODO: New API (JsonException)
throw new IllegalStateException("overloaded use of exclusiveMinimum or exclusiveMaximum keyword");
}
}
Expand Down
73 changes: 12 additions & 61 deletions core/src/main/java/org/everit/json/schema/ObjectComparator.java
Original file line number Diff line number Diff line change
@@ -1,78 +1,29 @@
package org.everit.json.schema;

import java.util.Arrays;
import java.util.Objects;

import org.everit.json.schema.facade.Facade;
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,
* {@link org.everit.json.schema.facade.JsonObject} and
* {@link JSONArray}.
*/
@Deprecated
public final class ObjectComparator {

/**
* Deep-equals implementation on primitive wrappers, {@link JSONObject} and {@link JSONArray}.
* Deep-equals implementation on primitive wrappers,
* {@link org.everit.json.schema.facade.JsonObject} and
* {@link JSONArray}.
*
* @param obj1
* the first object to be inspected
* @param obj2
* the second object to be inspected
* @param obj1 the first object to be inspected
* @param obj2 the second object to be inspected
* @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)) {
return false;
}
return deepEqualArrays((JSONArray) obj1, (JSONArray) obj2);
} else if (obj1 instanceof JSONObject) {
if (!(obj2 instanceof JSONObject)) {
return false;
}
return deepEqualObjects((JSONObject) obj1, (JSONObject) obj2);
}
return Objects.equals(obj1, obj2);
}

private static boolean deepEqualArrays(JSONArray arr1, JSONArray arr2) {
if (arr1.length() != arr2.length()) {
return false;
}
for (int i = 0; i < arr1.length(); ++i) {
if (!deepEquals(arr1.get(i), arr2.get(i))) {
return false;
}
}
return true;
}

private static String[] sortedNamesOf(JSONObject obj) {
String[] raw = JSONObject.getNames(obj);
if (raw == null) {
return null;
}
Arrays.sort(raw, String.CASE_INSENSITIVE_ORDER);
return raw;
}

private static boolean deepEqualObjects(JSONObject jsonObj1, JSONObject jsonObj2) {
String[] obj1Names = sortedNamesOf(jsonObj1);
if (!Arrays.equals(obj1Names, sortedNamesOf(jsonObj2))) {
return false;
}
if (obj1Names == null) {
return true;
}
for (String name : obj1Names) {
if (!deepEquals(jsonObj1.get(name), jsonObj2.get(name))) {
return false;
}
}
return true;
return Facade.getInstance().comparator()
.deepEquals(obj1, obj2);
}

private ObjectComparator() {
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class ObjectSchemaValidatingVisitor extends Visitor {

private final Object subject;

// TODO: New API (JsonObject)
private JSONObject objSubject;

private ObjectSchema schema;
Expand All @@ -28,6 +29,7 @@ public ObjectSchemaValidatingVisitor(Object subject, ValidatingVisitor owner) {
}

@Override void visitObjectSchema(ObjectSchema objectSchema) {
// TODO: New API (JsonObject)
if (owner.passesTypeCheck(JSONObject.class, objectSchema.requiresObject(), objectSchema.isNullable())) {
objSubject = (JSONObject) subject;
objectSize = objSubject.length();
Expand Down Expand Up @@ -129,6 +131,7 @@ private boolean matchesAnyPattern(String key) {
}

@Override void visitPatternPropertySchema(Regexp propertyNamePattern, Schema schema) {
// TODO: New API (JsonObject)
String[] propNames = JSONObject.getNames(objSubject);
if (propNames == null || propNames.length == 0) {
return;
Expand Down
23 changes: 17 additions & 6 deletions core/src/main/java/org/everit/json/schema/Schema.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import java.io.StringWriter;
import java.util.Objects;

import org.everit.json.schema.facade.JsonWriter;
import org.everit.json.schema.internal.JSONPrinter;
import org.json.JSONWriter;
import org.json.JSONPointer;

/**
* Superclass of all other schema validator classes of this package.
Expand Down Expand Up @@ -237,14 +238,14 @@ public Boolean isWriteOnly() {
* Describes the instance as a JSONObject to {@code writer}.
* <p>
* First it adds the {@code "title} , {@code "description"} and {@code "id"} properties then calls
* {@link #describePropertiesTo(JSONPrinter)}, which will add the subclass-specific properties.
* {@link #describePropertiesTo(JsonWriter)}, which will add the subclass-specific properties.
* <p>
* It is used by {@link #toString()} to serialize the schema instance into its JSON representation.
*
* @param writer
* it will receive the schema description
*/
public void describeTo(JSONPrinter writer) {
public void describeTo(JsonWriter writer) {
writer.object();
writer.ifPresent("title", title);
writer.ifPresent("description", description);
Expand All @@ -257,16 +258,26 @@ public void describeTo(JSONPrinter writer) {
writer.endObject();
}

@Deprecated // See new API
public void describeTo(JSONPrinter writer) {
this.describeTo((JsonWriter) writer);
}

/**
* Subclasses are supposed to override this method to describe the subclass-specific attributes.
* This method is called by {@link #describeTo(JSONPrinter)} after adding the generic properties if
* This method is called by {@link #describeTo(JsonWriter)} after adding the generic properties if
* they are present ({@code id}, {@code title} and {@code description}). As a side effect,
* overriding subclasses don't have to open and close the object with {@link JSONWriter#object()}
* and {@link JSONWriter#endObject()}.
* overriding subclasses don't have to open and close the object with {@link JsonWriter#object()}
* and {@link JsonWriter#endObject()}.
*
* @param writer
* it will receive the schema description
*/
void describePropertiesTo(JsonWriter writer) {
this.describePropertiesTo(new JSONPrinter(writer));
}

@Deprecated // See new API
void describePropertiesTo(JSONPrinter writer) {

}
Expand Down
28 changes: 18 additions & 10 deletions core/src/main/java/org/everit/json/schema/ValidationException.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import java.util.Objects;
import java.util.stream.Collectors;

import org.everit.json.schema.facade.Facade;
import org.everit.json.schema.facade.JsonObject;
import org.json.JSONArray;
import org.json.JSONObject;

Expand Down Expand Up @@ -426,21 +428,27 @@ public String getKeyword() {
*
* @return a JSON description of the validation error
*/
// TODO: New API (Easy)
@Deprecated
public JSONObject toJSON() {
JSONObject rval = new JSONObject();
rval.put("keyword", keyword);
return toJson().unsafe(JSONObject.class);
}

public JsonObject toJson() {
JsonObject rval = Facade.getInstance().object();
rval.set("keyword", keyword);
if (pointerToViolation == null) {
rval.put("pointerToViolation", JSONObject.NULL);
rval.set("pointerToViolation", Facade.getInstance().NULL());
} else {
rval.put("pointerToViolation", getPointerToViolation());
rval.set("pointerToViolation", getPointerToViolation());
}
rval.put("message", super.getMessage());
List<JSONObject> causeJsons = causingExceptions.stream()
.map(ValidationException::toJSON)
.collect(Collectors.toList());
rval.put("causingExceptions", new JSONArray(causeJsons));
rval.set("message", super.getMessage());
List<Object> causeJsons = causingExceptions.stream()
.map(ValidationException::toJson)
.collect(Collectors.toList());
rval.set("causingExceptions", new JSONArray(causeJsons));
if (schemaLocation != null) {
rval.put("schemaLocation", schemaLocation);
rval.set("schemaLocation", schemaLocation);
}
return rval;
}
Expand Down
54 changes: 54 additions & 0 deletions core/src/main/java/org/everit/json/schema/facade/Facade.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.everit.json.schema.facade;

import org.everit.json.schema.facade.orgjson.JsonOrg;

import java.io.Writer;
import java.util.Collection;
import java.util.Iterator;
import java.util.ServiceLoader;

public final class Facade {
private static final ServiceLoader<FacadeImpl> LOADER = ServiceLoader.load(FacadeImpl.class);
private static FacadeImpl instance;

private Facade() { // Utility Class
throw new Error();
}

// TODO: Document
// Allows overriding when ServiceLoader is not feasible.
public static void setInstance(FacadeImpl instance) {
Facade.instance = instance;
}

public static FacadeImpl getInstance() {
if (instance == null) {
load();
}
return instance;
}

private static void load() {
LOADER.reload();
Iterator<FacadeImpl> it = LOADER.iterator();
if (!it.hasNext()) {
// TODO: Remove
instance = new JsonOrg();
return;
// throw new IllegalStateException("Missing Json Implementation. Are you sure it is on the classpath?");
}
instance = it.next();
}

public interface FacadeImpl {
Object NULL();

JsonObject object();

JsonArray array(Collection<Object> elements);

JsonWriter writer(Writer writer);

JsonComparator comparator();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.everit.json.schema.facade;

public interface JsonArray extends JsonElement {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.everit.json.schema.facade;

import org.json.JSONArray;

/**
* Deep-equals implementation on primitive wrappers, {@link JsonObject} and {@link JSONArray}.
*/
public interface JsonComparator {
/**
* Deep-equals implementation on primitive wrappers, {@link JsonObject} and {@link JSONArray}.
*
* @param obj1 the first object to be inspected
* @param obj2 the second object to be inspected
* @return {@code true} if the two objects are equal, {@code false} otherwise
*/
boolean deepEquals(Object obj1, Object obj2);
}
17 changes: 17 additions & 0 deletions core/src/main/java/org/everit/json/schema/facade/JsonElement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.everit.json.schema.facade;

public interface JsonElement {
// Turns this Element into an arbitrary Object. Mainly intended for backwards compatibility.
// May throw UnsupportedOperationException if Object type is not supported. E.g. Json Org expected but not provided
// Type may not be null => NPE
// Subclasses should call parent / interface
default <T> T unsafe(Class<T> type) {
throw new UnsupportedOperationException(
String.format(
"Representing '%s' as '%s' is not supported.\nPlease consult the documentation of your Facade",
this.getClass().getCanonicalName(),
type.getCanonicalName()
)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.everit.json.schema.facade;

public interface JsonObject extends JsonElement {
void set(String key, Object value);
}
Loading