Skip to content

Commit 8e47194

Browse files
committed
Initial Work on Modular Design
JSON is widespread and many applications leverage it already. Adding support for Schemas to a existing Solution might cause some hindrance due to the various Json Implementations (e.g. Gson, org.json, jackson) currently existing. Relying on a specific JSON implementation requires consuming parties to choose between libraries that support their own library or write conversion code from and to a library of their choice. This work tries to introduce a new modular design, without breaking any existing code relying on org.json. After rewriting the Infrastructure it might be a good idea to remove some/all deprecation though. Signed-off-by: Bjoern Heinrichs <bjoern@heinrichs.pro>
1 parent e24e578 commit 8e47194

28 files changed

+421
-100
lines changed

core/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,13 @@
143143
</plugins>
144144
</build>
145145
<dependencies>
146+
<!-- TODO: Move into own artifact-->
146147
<dependency>
147148
<groupId>org.json</groupId>
148149
<artifactId>json</artifactId>
149150
<version>20180130</version>
150151
</dependency>
152+
<!-- -->
151153
<dependency>
152154
<groupId>com.google.guava</groupId>
153155
<artifactId>guava</artifactId>

core/src/main/java/org/everit/json/schema/ArraySchemaValidatingVisitor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class ArraySchemaValidatingVisitor extends Visitor {
1818

1919
private final ValidatingVisitor owner;
2020

21+
// TODO: New API (JsonArray)
2122
private JSONArray arraySubject;
2223

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

3233
@Override void visitArraySchema(ArraySchema arraySchema) {
34+
// TODO: New API (JsonArray)
3335
if (owner.passesTypeCheck(JSONArray.class, arraySchema.requiresArray(), arraySchema.isNullable())) {
3436
this.arraySubject = (JSONArray) subject;
3537
this.subjectLength = arraySubject.length();

core/src/main/java/org/everit/json/schema/EnumSchema.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@
1818
public class EnumSchema extends Schema {
1919

2020
static Object toJavaValue(Object orig) {
21+
// TODO: New API (JsonArray)
2122
if (orig instanceof JSONArray) {
2223
return ((JSONArray) orig).toList();
24+
// TODO: New API (JsonObject)
2325
} else if (orig instanceof JSONObject) {
2426
return ((JSONObject) orig).toMap();
27+
// TODO: New API (Null)
2528
} else if (orig == JSONObject.NULL) {
2629
return null;
2730
} else {

core/src/main/java/org/everit/json/schema/NumberSchema.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ void describePropertiesTo(JSONPrinter writer) {
206206
try {
207207
writer.ifPresent("exclusiveMinimum", exclusiveMinimumLimit);
208208
writer.ifPresent("exclusiveMaximum", exclusiveMaximumLimit);
209-
} catch (JSONException e) {
209+
} catch (JSONException e) { // TODO: New API (JsonException)
210210
throw new IllegalStateException("overloaded use of exclusiveMinimum or exclusiveMaximum keyword");
211211
}
212212
}
Lines changed: 12 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,29 @@
11
package org.everit.json.schema;
22

3-
import java.util.Arrays;
4-
import java.util.Objects;
5-
3+
import org.everit.json.schema.facade.Facade;
64
import org.json.JSONArray;
7-
import org.json.JSONObject;
85

96
/**
10-
* Deep-equals implementation on primitive wrappers, {@link JSONObject} and {@link JSONArray}.
7+
* Deep-equals implementation on primitive wrappers,
8+
* {@link org.everit.json.schema.facade.JsonObject} and
9+
* {@link JSONArray}.
1110
*/
11+
@Deprecated
1212
public final class ObjectComparator {
13-
1413
/**
15-
* Deep-equals implementation on primitive wrappers, {@link JSONObject} and {@link JSONArray}.
14+
* Deep-equals implementation on primitive wrappers,
15+
* {@link org.everit.json.schema.facade.JsonObject} and
16+
* {@link JSONArray}.
1617
*
17-
* @param obj1
18-
* the first object to be inspected
19-
* @param obj2
20-
* the second object to be inspected
18+
* @param obj1 the first object to be inspected
19+
* @param obj2 the second object to be inspected
2120
* @return {@code true} if the two objects are equal, {@code false} otherwise
2221
*/
2322
public static boolean deepEquals(Object obj1, Object obj2) {
24-
if (obj1 instanceof JSONArray) {
25-
if (!(obj2 instanceof JSONArray)) {
26-
return false;
27-
}
28-
return deepEqualArrays((JSONArray) obj1, (JSONArray) obj2);
29-
} else if (obj1 instanceof JSONObject) {
30-
if (!(obj2 instanceof JSONObject)) {
31-
return false;
32-
}
33-
return deepEqualObjects((JSONObject) obj1, (JSONObject) obj2);
34-
}
35-
return Objects.equals(obj1, obj2);
36-
}
37-
38-
private static boolean deepEqualArrays(JSONArray arr1, JSONArray arr2) {
39-
if (arr1.length() != arr2.length()) {
40-
return false;
41-
}
42-
for (int i = 0; i < arr1.length(); ++i) {
43-
if (!deepEquals(arr1.get(i), arr2.get(i))) {
44-
return false;
45-
}
46-
}
47-
return true;
48-
}
49-
50-
private static String[] sortedNamesOf(JSONObject obj) {
51-
String[] raw = JSONObject.getNames(obj);
52-
if (raw == null) {
53-
return null;
54-
}
55-
Arrays.sort(raw, String.CASE_INSENSITIVE_ORDER);
56-
return raw;
57-
}
58-
59-
private static boolean deepEqualObjects(JSONObject jsonObj1, JSONObject jsonObj2) {
60-
String[] obj1Names = sortedNamesOf(jsonObj1);
61-
if (!Arrays.equals(obj1Names, sortedNamesOf(jsonObj2))) {
62-
return false;
63-
}
64-
if (obj1Names == null) {
65-
return true;
66-
}
67-
for (String name : obj1Names) {
68-
if (!deepEquals(jsonObj1.get(name), jsonObj2.get(name))) {
69-
return false;
70-
}
71-
}
72-
return true;
23+
return Facade.getInstance().comparator()
24+
.deepEquals(obj1, obj2);
7325
}
7426

7527
private ObjectComparator() {
7628
}
77-
7829
}

core/src/main/java/org/everit/json/schema/ObjectSchemaValidatingVisitor.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class ObjectSchemaValidatingVisitor extends Visitor {
1414

1515
private final Object subject;
1616

17+
// TODO: New API (JsonObject)
1718
private JSONObject objSubject;
1819

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

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

131133
@Override void visitPatternPropertySchema(Regexp propertyNamePattern, Schema schema) {
134+
// TODO: New API (JsonObject)
132135
String[] propNames = JSONObject.getNames(objSubject);
133136
if (propNames == null || propNames.length == 0) {
134137
return;

core/src/main/java/org/everit/json/schema/Schema.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import java.io.StringWriter;
44
import java.util.Objects;
55

6+
import org.everit.json.schema.facade.JsonWriter;
67
import org.everit.json.schema.internal.JSONPrinter;
7-
import org.json.JSONWriter;
8+
import org.json.JSONPointer;
89

910
/**
1011
* Superclass of all other schema validator classes of this package.
@@ -237,14 +238,14 @@ public Boolean isWriteOnly() {
237238
* Describes the instance as a JSONObject to {@code writer}.
238239
* <p>
239240
* First it adds the {@code "title} , {@code "description"} and {@code "id"} properties then calls
240-
* {@link #describePropertiesTo(JSONPrinter)}, which will add the subclass-specific properties.
241+
* {@link #describePropertiesTo(JsonWriter)}, which will add the subclass-specific properties.
241242
* <p>
242243
* It is used by {@link #toString()} to serialize the schema instance into its JSON representation.
243244
*
244245
* @param writer
245246
* it will receive the schema description
246247
*/
247-
public void describeTo(JSONPrinter writer) {
248+
public void describeTo(JsonWriter writer) {
248249
writer.object();
249250
writer.ifPresent("title", title);
250251
writer.ifPresent("description", description);
@@ -257,16 +258,26 @@ public void describeTo(JSONPrinter writer) {
257258
writer.endObject();
258259
}
259260

261+
@Deprecated // See new API
262+
public void describeTo(JSONPrinter writer) {
263+
this.describeTo((JsonWriter) writer);
264+
}
265+
260266
/**
261267
* Subclasses are supposed to override this method to describe the subclass-specific attributes.
262-
* This method is called by {@link #describeTo(JSONPrinter)} after adding the generic properties if
268+
* This method is called by {@link #describeTo(JsonWriter)} after adding the generic properties if
263269
* they are present ({@code id}, {@code title} and {@code description}). As a side effect,
264-
* overriding subclasses don't have to open and close the object with {@link JSONWriter#object()}
265-
* and {@link JSONWriter#endObject()}.
270+
* overriding subclasses don't have to open and close the object with {@link JsonWriter#object()}
271+
* and {@link JsonWriter#endObject()}.
266272
*
267273
* @param writer
268274
* it will receive the schema description
269275
*/
276+
void describePropertiesTo(JsonWriter writer) {
277+
this.describePropertiesTo(new JSONPrinter(writer));
278+
}
279+
280+
@Deprecated // See new API
270281
void describePropertiesTo(JSONPrinter writer) {
271282

272283
}

core/src/main/java/org/everit/json/schema/ValidationException.java

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import java.util.Objects;
99
import java.util.stream.Collectors;
1010

11+
import org.everit.json.schema.facade.Facade;
12+
import org.everit.json.schema.facade.JsonObject;
1113
import org.json.JSONArray;
1214
import org.json.JSONObject;
1315

@@ -426,21 +428,27 @@ public String getKeyword() {
426428
*
427429
* @return a JSON description of the validation error
428430
*/
431+
// TODO: New API (Easy)
432+
@Deprecated
429433
public JSONObject toJSON() {
430-
JSONObject rval = new JSONObject();
431-
rval.put("keyword", keyword);
434+
return toJson().unsafe(JSONObject.class);
435+
}
436+
437+
public JsonObject toJson() {
438+
JsonObject rval = Facade.getInstance().object();
439+
rval.set("keyword", keyword);
432440
if (pointerToViolation == null) {
433-
rval.put("pointerToViolation", JSONObject.NULL);
441+
rval.set("pointerToViolation", Facade.getInstance().NULL());
434442
} else {
435-
rval.put("pointerToViolation", getPointerToViolation());
443+
rval.set("pointerToViolation", getPointerToViolation());
436444
}
437-
rval.put("message", super.getMessage());
438-
List<JSONObject> causeJsons = causingExceptions.stream()
439-
.map(ValidationException::toJSON)
440-
.collect(Collectors.toList());
441-
rval.put("causingExceptions", new JSONArray(causeJsons));
445+
rval.set("message", super.getMessage());
446+
List<Object> causeJsons = causingExceptions.stream()
447+
.map(ValidationException::toJson)
448+
.collect(Collectors.toList());
449+
rval.set("causingExceptions", new JSONArray(causeJsons));
442450
if (schemaLocation != null) {
443-
rval.put("schemaLocation", schemaLocation);
451+
rval.set("schemaLocation", schemaLocation);
444452
}
445453
return rval;
446454
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package org.everit.json.schema.facade;
2+
3+
import org.everit.json.schema.facade.orgjson.JsonOrg;
4+
5+
import java.io.Writer;
6+
import java.util.Collection;
7+
import java.util.Iterator;
8+
import java.util.ServiceLoader;
9+
10+
public final class Facade {
11+
private static final ServiceLoader<FacadeImpl> LOADER = ServiceLoader.load(FacadeImpl.class);
12+
private static FacadeImpl instance;
13+
14+
private Facade() { // Utility Class
15+
throw new Error();
16+
}
17+
18+
// TODO: Document
19+
// Allows overriding when ServiceLoader is not feasible.
20+
public static void setInstance(FacadeImpl instance) {
21+
Facade.instance = instance;
22+
}
23+
24+
public static FacadeImpl getInstance() {
25+
if (instance == null) {
26+
load();
27+
}
28+
return instance;
29+
}
30+
31+
private static void load() {
32+
LOADER.reload();
33+
Iterator<FacadeImpl> it = LOADER.iterator();
34+
if (!it.hasNext()) {
35+
// TODO: Remove
36+
instance = new JsonOrg();
37+
return;
38+
// throw new IllegalStateException("Missing Json Implementation. Are you sure it is on the classpath?");
39+
}
40+
instance = it.next();
41+
}
42+
43+
public interface FacadeImpl {
44+
Object NULL();
45+
46+
JsonObject object();
47+
48+
JsonArray array(Collection<Object> elements);
49+
50+
JsonWriter writer(Writer writer);
51+
52+
JsonComparator comparator();
53+
}
54+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package org.everit.json.schema.facade;
2+
3+
public interface JsonArray extends JsonElement {
4+
}

0 commit comments

Comments
 (0)