Skip to content

Commit 8c97759

Browse files
author
Lionel Montrieux
committed
Create lists of ValidationError only when necessary
This commit reduces memory usage by only creating a list of ValidationException objects when the first one in encountered. JSONs that are valid will gain the most in terms of memory usage, as no lists will be created. JSONs that are only partially valid will still see memory usage improvements.
1 parent d607795 commit 8c97759

16 files changed

+196
-131
lines changed

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

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package org.everit.json.schema;
22

3-
import static java.lang.String.format;
4-
import static java.util.Objects.requireNonNull;
3+
import org.everit.json.schema.internal.JSONPrinter;
4+
import org.json.JSONArray;
55

66
import java.util.ArrayList;
77
import java.util.Collection;
@@ -12,8 +12,8 @@
1212
import java.util.function.IntFunction;
1313
import java.util.stream.IntStream;
1414

15-
import org.everit.json.schema.internal.JSONPrinter;
16-
import org.json.JSONArray;
15+
import static java.lang.String.format;
16+
import static java.util.Objects.requireNonNull;
1717

1818
/**
1919
* Array schema validator.
@@ -200,44 +200,42 @@ public boolean requiresArray() {
200200
return requiresArray;
201201
}
202202

203-
private Optional<ValidationException> testItemCount(final JSONArray subject) {
203+
private void testItemCount(final JSONArray subject) {
204204
int actualLength = subject.length();
205205
if (minItems != null && actualLength < minItems) {
206-
return Optional.of(failure("expected minimum item count: " + minItems
206+
addValidationException(failure("expected minimum item count: " + minItems
207207
+ ", found: " + actualLength, "minItems"));
208+
return;
208209
}
209210
if (maxItems != null && maxItems < actualLength) {
210-
return Optional.of(failure("expected maximum item count: " + maxItems
211+
addValidationException(failure("expected maximum item count: " + maxItems
211212
+ ", found: " + actualLength, "maxItems"));
212213
}
213-
return Optional.empty();
214214
}
215215

216-
private List<ValidationException> testItems(final JSONArray subject) {
217-
List<ValidationException> rval = new ArrayList<>();
216+
private void testItems(final JSONArray subject) {
218217
if (allItemSchema != null) {
219218
validateItemsAgainstSchema(IntStream.range(0, subject.length()),
220219
subject,
221220
allItemSchema,
222-
rval::add);
221+
this::addValidationException);
223222
} else if (itemSchemas != null) {
224223
if (!additionalItems && subject.length() > itemSchemas.size()) {
225-
rval.add(failure(format("expected: [%d] array items, found: [%d]",
224+
addValidationException(failure(format("expected: [%d] array items, found: [%d]",
226225
itemSchemas.size(), subject.length()), "items"));
227226
}
228227
int itemValidationUntil = Math.min(subject.length(), itemSchemas.size());
229228
validateItemsAgainstSchema(IntStream.range(0, itemValidationUntil),
230229
subject,
231230
itemSchemas::get,
232-
rval::add);
231+
this::addValidationException);
233232
if (schemaOfAdditionalItems != null) {
234233
validateItemsAgainstSchema(IntStream.range(itemValidationUntil, subject.length()),
235234
subject,
236235
schemaOfAdditionalItems,
237-
rval::add);
236+
this::addValidationException);
238237
}
239238
}
240-
return rval;
241239
}
242240

243241
private void validateItemsAgainstSchema(final IntStream indices, final JSONArray items,
@@ -257,57 +255,57 @@ private void validateItemsAgainstSchema(final IntStream indices, final JSONArray
257255
}
258256
}
259257

260-
private Optional<ValidationException> testUniqueness(final JSONArray subject) {
258+
private void testUniqueness(final JSONArray subject) {
261259
if (subject.length() == 0) {
262-
return Optional.empty();
260+
return;
263261
}
264262
Collection<Object> uniqueItems = new ArrayList<Object>(subject.length());
265263
for (int i = 0; i < subject.length(); ++i) {
266264
Object item = subject.get(i);
267265
for (Object contained : uniqueItems) {
268266
if (ObjectComparator.deepEquals(contained, item)) {
269-
return Optional.of(
267+
addValidationException(
270268
failure("array items are not unique", "uniqueItems"));
269+
return;
271270
}
272271
}
273272
uniqueItems.add(item);
274273
}
275-
return Optional.empty();
276274
}
277275

278276
@Override
279277
public void validate(final Object subject) {
280-
List<ValidationException> failures = new ArrayList<>();
281278
if (!(subject instanceof JSONArray)) {
282279
if (requiresArray) {
283280
throw failure(JSONArray.class, subject);
284281
}
285282
} else {
283+
validationExceptions = null;
286284
JSONArray arrSubject = (JSONArray) subject;
287-
testItemCount(arrSubject).ifPresent(failures::add);
285+
testItemCount(arrSubject);
288286
if (uniqueItems) {
289-
testUniqueness(arrSubject).ifPresent(failures::add);
287+
testUniqueness(arrSubject);
290288
}
291-
failures.addAll(testItems(arrSubject));
292-
testContains(arrSubject).ifPresent(failures::add);
289+
testItems(arrSubject);
290+
testContains(arrSubject);
291+
}
292+
if (null != validationExceptions) {
293+
ValidationException.throwFor(this, validationExceptions);
293294
}
294-
ValidationException.throwFor(this, failures);
295295
}
296296

297-
private Optional<ValidationException> testContains(JSONArray arrSubject) {
297+
private void testContains(JSONArray arrSubject) {
298298
if (containedItemSchema == null) {
299-
return Optional.empty();
299+
return;
300300
}
301301
boolean anyMatch = IntStream.range(0, arrSubject.length())
302302
.mapToObj(arrSubject::get)
303303
.map(item -> ifFails(containedItemSchema, item))
304304
.filter(maybeFailure -> !maybeFailure.isPresent())
305305
.findFirst()
306306
.isPresent();
307-
if (anyMatch) {
308-
return Optional.empty();
309-
} else {
310-
return Optional.of(failure("expected at least one array item to match 'contains' schema", "contains"));
307+
if (!anyMatch) {
308+
addValidationException(failure("expected at least one array item to match 'contains' schema", "contains"));
311309
}
312310
}
313311

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

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,23 @@
33
import org.everit.json.schema.internal.JSONPrinter;
44
import org.json.JSONObject;
55

6-
import java.util.*;
6+
import java.util.ArrayList;
7+
import java.util.Arrays;
8+
import java.util.Collection;
9+
import java.util.Collections;
10+
import java.util.HashMap;
11+
import java.util.HashSet;
12+
import java.util.List;
13+
import java.util.Map;
714
import java.util.Map.Entry;
15+
import java.util.Objects;
16+
import java.util.Optional;
17+
import java.util.Set;
818
import java.util.regex.Pattern;
9-
import java.util.stream.Collectors;
1019
import java.util.stream.Stream;
1120

1221
import static java.lang.String.format;
1322
import static java.util.Arrays.asList;
14-
import static java.util.Collections.emptyList;
1523
import static java.util.Objects.requireNonNull;
1624
import static java.util.stream.Collectors.toList;
1725

@@ -264,101 +272,92 @@ public boolean requiresObject() {
264272
return requiresObject;
265273
}
266274

267-
private List<ValidationException> testAdditionalProperties(final JSONObject subject) {
275+
private void testAdditionalProperties(final JSONObject subject) {
268276
if (!additionalProperties) {
269-
return getAdditionalProperties(subject)
277+
addValidationExceptions(getAdditionalProperties(subject)
270278
.map(unneeded -> format("extraneous key [%s] is not permitted", unneeded))
271279
.map(msg -> new ValidationException(this, msg, "additionalProperties"))
272-
.collect(toList());
280+
.collect(toList()));
281+
return;
273282
} else if (schemaOfAdditionalProperties != null) {
274283
List<String> additionalPropNames = getAdditionalProperties(subject)
275284
.collect(toList());
276-
List<ValidationException> rval = new ArrayList<ValidationException>();
277285
for (String propName : additionalPropNames) {
278286
Object propVal = subject.get(propName);
279287
ifFails(schemaOfAdditionalProperties, propVal)
280288
.map(failure -> failure.prepend(propName, this))
281-
.ifPresent(rval::add);
289+
.ifPresent(this::addValidationException);
282290
}
283-
return rval;
284291
}
285-
return emptyList();
286292
}
287293

288-
private List<ValidationException> testPatternProperties(final JSONObject subject) {
294+
private void testPatternProperties(final JSONObject subject) {
289295
String[] propNames = JSONObject.getNames(subject);
290296
if (propNames == null || propNames.length == 0) {
291-
return emptyList();
297+
return;
292298
}
293-
List<ValidationException> rval = new ArrayList<>();
294299
for (Entry<Pattern, Schema> entry : patternProperties.entrySet()) {
295300
for (String propName : propNames) {
296301
if (entry.getKey().matcher(propName).find()) {
297302
ifFails(entry.getValue(), subject.get(propName))
298303
.map(exc -> exc.prepend(propName))
299-
.ifPresent(rval::add);
304+
.ifPresent(this::addValidationException);
300305
}
301306
}
302307
}
303-
return rval;
304308
}
305309

306-
private List<ValidationException> testProperties(final JSONObject subject) {
310+
private void testProperties(final JSONObject subject) {
307311
if (propertySchemas != null) {
308-
List<ValidationException> rval = new ArrayList<>();
309312
for (Entry<String, Schema> entry : propertySchemas.entrySet()) {
310313
String key = entry.getKey();
311314
if (subject.has(key)) {
312315
ifFails(entry.getValue(), subject.get(key))
313316
.map(exc -> exc.prepend(key))
314-
.ifPresent(rval::add);
317+
.ifPresent(this::addValidationException);
315318
}
316319
}
317-
return rval;
318320
}
319-
return emptyList();
320321
}
321322

322-
private List<ValidationException> testPropertyDependencies(final JSONObject subject) {
323-
return propertyDependencies.keySet().stream()
323+
private void testPropertyDependencies(final JSONObject subject) {
324+
addValidationExceptions(propertyDependencies.keySet().stream()
324325
.filter(subject::has)
325326
.flatMap(ifPresent -> propertyDependencies.get(ifPresent).stream())
326327
.filter(mustBePresent -> !subject.has(mustBePresent))
327328
.map(missingKey -> format("property [%s] is required", missingKey))
328329
.map(excMessage -> failure(excMessage, "dependencies"))
329-
.collect(toList());
330+
.collect(toList()));
330331
}
331332

332-
private List<ValidationException> testRequiredProperties(final JSONObject subject) {
333-
return requiredProperties.stream()
333+
private void testRequiredProperties(final JSONObject subject) {
334+
addValidationExceptions(requiredProperties.stream()
334335
.filter(key -> !subject.has(key))
335336
.map(missingKey -> format("required key [%s] not found", missingKey))
336337
.map(excMessage -> failure(excMessage, "required"))
337-
.collect(toList());
338+
.collect(toList()));
338339
}
339340

340-
private List<ValidationException> testSchemaDependencies(final JSONObject subject) {
341-
List<ValidationException> rval = new ArrayList<>();
341+
private void testSchemaDependencies(final JSONObject subject) {
342342
for (Map.Entry<String, Schema> schemaDep : schemaDependencies.entrySet()) {
343343
String propName = schemaDep.getKey();
344344
if (subject.has(propName)) {
345-
ifFails(schemaDep.getValue(), subject).ifPresent(rval::add);
345+
ifFails(schemaDep.getValue(), subject).ifPresent(this::addValidationException);
346346
}
347347
}
348-
return rval;
349348
}
350349

351-
private List<ValidationException> testSize(final JSONObject subject) {
350+
private void testSize(final JSONObject subject) {
352351
int actualSize = subject.length();
353352
if (minProperties != null && actualSize < minProperties.intValue()) {
354-
return asList(failure(format("minimum size: [%d], found: [%d]", minProperties, actualSize),
355-
"minProperties"));
353+
addValidationExceptions(asList(failure(format("minimum size: [%d], found: [%d]", minProperties, actualSize),
354+
"minProperties")));
355+
return;
356356
}
357357
if (maxProperties != null && actualSize > maxProperties.intValue()) {
358-
return asList(failure(format("maximum size: [%d], found: [%d]", maxProperties, actualSize),
359-
"maxProperties"));
358+
addValidationExceptions(asList(failure(format("maximum size: [%d], found: [%d]", maxProperties, actualSize),
359+
"maxProperties")));
360360
}
361-
return emptyList();
362361
}
363362

364363
@Override
@@ -368,25 +367,27 @@ public void validate(final Object subject) {
368367
throw failure(JSONObject.class, subject);
369368
}
370369
} else {
371-
List<ValidationException> failures = new ArrayList<>();
370+
validationExceptions = null;
372371
JSONObject objSubject = (JSONObject) subject;
373-
failures.addAll(testProperties(objSubject));
374-
failures.addAll(testRequiredProperties(objSubject));
375-
failures.addAll(testAdditionalProperties(objSubject));
376-
failures.addAll(testSize(objSubject));
377-
failures.addAll(testPropertyDependencies(objSubject));
378-
failures.addAll(testSchemaDependencies(objSubject));
379-
failures.addAll(testPatternProperties(objSubject));
380-
failures.addAll(testPropertyNames(objSubject));
381-
ValidationException.throwFor(this, failures);
372+
testProperties(objSubject);
373+
testRequiredProperties(objSubject);
374+
testAdditionalProperties(objSubject);
375+
testSize(objSubject);
376+
testPropertyDependencies(objSubject);
377+
testSchemaDependencies(objSubject);
378+
testPatternProperties(objSubject);
379+
testPropertyNames(objSubject);
380+
if (null != validationExceptions) {
381+
ValidationException.throwFor(this, validationExceptions);
382+
}
382383
}
383384
}
384385

385-
private Collection<? extends ValidationException> testPropertyNames(JSONObject subject) {
386+
private void testPropertyNames(JSONObject subject) {
386387
if (propertyNameSchema != null) {
387388
String[] names = JSONObject.getNames(subject);
388389
if (names == null || names.length == 0) {
389-
return emptyList();
390+
return;
390391
}
391392
Collection<ValidationException> failures = Arrays.stream(names)
392393
.map(name -> {
@@ -399,9 +400,8 @@ private Collection<? extends ValidationException> testPropertyNames(JSONObject s
399400
})
400401
.filter(Objects::nonNull)
401402
.collect(toList());
402-
return failures;
403+
validationExceptions.addAll(failures);
403404
}
404-
return emptyList();
405405
}
406406

407407
@Override

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package org.everit.json.schema;
22

33
import org.everit.json.schema.internal.JSONPrinter;
4-
import org.json.JSONPointer;
54
import org.json.JSONWriter;
65

76
import java.io.StringWriter;
7+
import java.util.ArrayList;
8+
import java.util.Collection;
9+
import java.util.List;
810
import java.util.Objects;
911

1012
/**
@@ -61,6 +63,8 @@ public Builder<S> schemaLocation(String schemaLocation) {
6163

6264
protected final String schemaLocation;
6365

66+
protected List<ValidationException> validationExceptions = null;
67+
6468
/**
6569
* Constructor.
6670
*
@@ -222,4 +226,18 @@ protected ValidationException failure(Class<?> expectedType, Object actualValue)
222226
protected boolean canEqual(final Object other) {
223227
return (other instanceof Schema);
224228
}
229+
230+
protected void addValidationException(ValidationException e) {
231+
if (null == validationExceptions) {
232+
validationExceptions = new ArrayList<>();
233+
}
234+
validationExceptions.add(e);
235+
}
236+
237+
protected void addValidationExceptions(Collection<ValidationException> c) {
238+
if (null == validationExceptions) {
239+
validationExceptions = new ArrayList<>();
240+
}
241+
validationExceptions.addAll(c);
242+
}
225243
}

0 commit comments

Comments
 (0)