Skip to content

Commit 7623862

Browse files
author
Lionel Montrieux
committed
Optimise validation memory usage by not using stream API
Using the stream API results in the creation of quite a lot of objects. In scenarios where a lot of JSONs are validated, this causes frequent, and large, garbage collection. In this commit, maps are replaced with for loops, and filters with if statements.
1 parent 8c97759 commit 7623862

File tree

6 files changed

+114
-102
lines changed

6 files changed

+114
-102
lines changed

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

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -298,15 +298,13 @@ private void testContains(JSONArray arrSubject) {
298298
if (containedItemSchema == null) {
299299
return;
300300
}
301-
boolean anyMatch = IntStream.range(0, arrSubject.length())
302-
.mapToObj(arrSubject::get)
303-
.map(item -> ifFails(containedItemSchema, item))
304-
.filter(maybeFailure -> !maybeFailure.isPresent())
305-
.findFirst()
306-
.isPresent();
307-
if (!anyMatch) {
308-
addValidationException(failure("expected at least one array item to match 'contains' schema", "contains"));
301+
for (int i = 0; i < arrSubject.length(); i++) {
302+
Optional<ValidationException> exception = ifFails(containedItemSchema, arrSubject.get(i));
303+
if (!exception.isPresent()) {
304+
return;
305+
}
309306
}
307+
addValidationException(failure("expected at least one array item to match 'contains' schema", "contains"));
310308
}
311309

312310
@Override

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

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
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;
54

65
import java.util.ArrayList;
76
import java.util.Collection;
87
import java.util.List;
98
import java.util.Objects;
10-
import java.util.stream.Collectors;
119

12-
import org.everit.json.schema.internal.JSONPrinter;
10+
import static java.lang.String.format;
11+
import static java.util.Objects.requireNonNull;
1312

1413
/**
1514
* Validator for {@code allOf}, {@code oneOf}, {@code anyOf} schemas.
@@ -183,10 +182,13 @@ private ValidationException getFailure(final Schema schema, final Object subject
183182

184183
@Override
185184
public void validate(final Object subject) {
186-
List<ValidationException> failures = subschemas.stream()
187-
.map(schema -> getFailure(schema, subject))
188-
.filter(Objects::nonNull)
189-
.collect(Collectors.toList());
185+
List<ValidationException> failures = new ArrayList<>();
186+
for (Schema subschema: subschemas) {
187+
ValidationException exception = getFailure(subschema, subject);
188+
if (null != exception) {
189+
failures.add(exception);
190+
}
191+
}
190192
int matchingCount = subschemas.size() - failures.size();
191193
try {
192194
criterion.validate(subschemas.size(), matchingCount);
@@ -202,9 +204,12 @@ public void validate(final Object subject) {
202204

203205
@Override
204206
public boolean definesProperty(final String field) {
205-
List<Schema> matching = subschemas.stream()
206-
.filter(schema -> schema.definesProperty(field))
207-
.collect(Collectors.toList());
207+
List<Schema> matching = new ArrayList<>();
208+
for (Schema subschema: subschemas) {
209+
if (subschema.definesProperty(field)) {
210+
matching.add(subschema);
211+
}
212+
}
208213
try {
209214
criterion.validate(subschemas.size(), matching.size());
210215
} catch (ValidationException e) {

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

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

3-
import static java.lang.String.format;
4-
import static java.util.stream.Collectors.toSet;
3+
import org.everit.json.schema.internal.JSONPrinter;
4+
import org.json.JSONArray;
5+
import org.json.JSONObject;
56

67
import java.util.Collections;
78
import java.util.HashSet;
89
import java.util.Objects;
910
import java.util.Set;
1011

11-
import org.everit.json.schema.internal.JSONPrinter;
12-
import org.json.JSONArray;
13-
import org.json.JSONObject;
12+
import static java.lang.String.format;
13+
import static java.util.stream.Collectors.toSet;
1414

1515
/**
1616
* Enum schema validator.
@@ -72,12 +72,12 @@ public Set<Object> getPossibleValues() {
7272
@Override
7373
public void validate(final Object subject) {
7474
Object effectiveSubject = toJavaValue(subject);
75-
possibleValues
76-
.stream()
77-
.filter(val -> ObjectComparator.deepEquals(val, effectiveSubject))
78-
.findAny()
79-
.orElseThrow(
80-
() -> failure(format("%s is not a valid enum value", subject), "enum"));
75+
for (Object possibleValue: possibleValues) {
76+
if (ObjectComparator.deepEquals(possibleValue, effectiveSubject)) {
77+
return;
78+
}
79+
}
80+
throw failure(format("%s is not a valid enum value", subject), "enum");
8181
}
8282

8383
@Override

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

Lines changed: 74 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
import org.json.JSONObject;
55

66
import java.util.ArrayList;
7-
import java.util.Arrays;
8-
import java.util.Collection;
97
import java.util.Collections;
108
import java.util.HashMap;
119
import java.util.HashSet;
@@ -16,12 +14,10 @@
1614
import java.util.Optional;
1715
import java.util.Set;
1816
import java.util.regex.Pattern;
19-
import java.util.stream.Stream;
2017

2118
import static java.lang.String.format;
2219
import static java.util.Arrays.asList;
2320
import static java.util.Objects.requireNonNull;
24-
import static java.util.stream.Collectors.toList;
2521

2622
/**
2723
* Object schema validator.
@@ -200,15 +196,18 @@ public ObjectSchema(final Builder builder) {
200196
this.propertyNameSchema = builder.propertyNameSchema;
201197
}
202198

203-
private Stream<String> getAdditionalProperties(final JSONObject subject) {
199+
private List<String> getAdditionalProperties(final JSONObject subject) {
204200
String[] names = JSONObject.getNames(subject);
205201
if (names == null) {
206-
return Stream.empty();
202+
return new ArrayList<>();
207203
} else {
208-
return Arrays
209-
.stream(names)
210-
.filter(key -> !propertySchemas.containsKey(key))
211-
.filter(key -> !matchesAnyPattern(key));
204+
List<String> namesList = new ArrayList<>();
205+
for (String name:names) {
206+
if (!propertySchemas.containsKey(name) && !matchesAnyPattern(name)) {
207+
namesList.add(name);
208+
}
209+
}
210+
return namesList;
212211
}
213212
}
214213

@@ -258,10 +257,12 @@ private Optional<ValidationException> ifFails(final Schema schema, final Object
258257
}
259258

260259
private boolean matchesAnyPattern(final String key) {
261-
return patternProperties.keySet().stream()
262-
.filter(pattern -> pattern.matcher(key).find())
263-
.findAny()
264-
.isPresent();
260+
for (Pattern pattern: patternProperties.keySet()) {
261+
if (pattern.matcher(key).find()) {
262+
return true;
263+
}
264+
}
265+
return false;
265266
}
266267

267268
public boolean permitsAdditionalProperties() {
@@ -274,19 +275,23 @@ public boolean requiresObject() {
274275

275276
private void testAdditionalProperties(final JSONObject subject) {
276277
if (!additionalProperties) {
277-
addValidationExceptions(getAdditionalProperties(subject)
278-
.map(unneeded -> format("extraneous key [%s] is not permitted", unneeded))
279-
.map(msg -> new ValidationException(this, msg, "additionalProperties"))
280-
.collect(toList()));
278+
List<String> additionalProperties = getAdditionalProperties(subject);
279+
if (null == additionalProperties || additionalProperties.isEmpty()) {
280+
return;
281+
}
282+
for (String additionalProperty: additionalProperties) {
283+
addValidationException(new ValidationException(this,
284+
format("extraneous key [%s] is not permitted", additionalProperty), "additionalProperties"));
285+
}
281286
return;
282287
} else if (schemaOfAdditionalProperties != null) {
283-
List<String> additionalPropNames = getAdditionalProperties(subject)
284-
.collect(toList());
288+
List<String> additionalPropNames = getAdditionalProperties(subject);
285289
for (String propName : additionalPropNames) {
286290
Object propVal = subject.get(propName);
287-
ifFails(schemaOfAdditionalProperties, propVal)
288-
.map(failure -> failure.prepend(propName, this))
289-
.ifPresent(this::addValidationException);
291+
Optional<ValidationException> exception = ifFails(schemaOfAdditionalProperties, propVal);
292+
if (exception.isPresent()) {
293+
addValidationException(exception.get().prepend(propName, this));
294+
}
290295
}
291296
}
292297
}
@@ -299,9 +304,10 @@ private void testPatternProperties(final JSONObject subject) {
299304
for (Entry<Pattern, Schema> entry : patternProperties.entrySet()) {
300305
for (String propName : propNames) {
301306
if (entry.getKey().matcher(propName).find()) {
302-
ifFails(entry.getValue(), subject.get(propName))
303-
.map(exc -> exc.prepend(propName))
304-
.ifPresent(this::addValidationException);
307+
Optional<ValidationException> exception = ifFails(entry.getValue(), subject.get(propName));
308+
if (exception.isPresent()) {
309+
addValidationException(exception.get().prepend(propName));
310+
}
305311
}
306312
}
307313
}
@@ -312,30 +318,33 @@ private void testProperties(final JSONObject subject) {
312318
for (Entry<String, Schema> entry : propertySchemas.entrySet()) {
313319
String key = entry.getKey();
314320
if (subject.has(key)) {
315-
ifFails(entry.getValue(), subject.get(key))
316-
.map(exc -> exc.prepend(key))
317-
.ifPresent(this::addValidationException);
321+
Optional<ValidationException> exception = ifFails(entry.getValue(), subject.get(key));
322+
if (exception.isPresent()) {
323+
addValidationException(exception.get().prepend(key));
324+
}
318325
}
319326
}
320327
}
321328
}
322329

323330
private void testPropertyDependencies(final JSONObject subject) {
324-
addValidationExceptions(propertyDependencies.keySet().stream()
325-
.filter(subject::has)
326-
.flatMap(ifPresent -> propertyDependencies.get(ifPresent).stream())
327-
.filter(mustBePresent -> !subject.has(mustBePresent))
328-
.map(missingKey -> format("property [%s] is required", missingKey))
329-
.map(excMessage -> failure(excMessage, "dependencies"))
330-
.collect(toList()));
331+
for (String property: propertyDependencies.keySet()) {
332+
if (subject.has(property)) {
333+
for (String mustBePresent : propertyDependencies.get(property)) {
334+
if (!subject.has(mustBePresent)) {
335+
addValidationException(failure(format("property [%s] is required", mustBePresent), "dependencies"));
336+
}
337+
}
338+
}
339+
}
331340
}
332341

333342
private void testRequiredProperties(final JSONObject subject) {
334-
addValidationExceptions(requiredProperties.stream()
335-
.filter(key -> !subject.has(key))
336-
.map(missingKey -> format("required key [%s] not found", missingKey))
337-
.map(excMessage -> failure(excMessage, "required"))
338-
.collect(toList()));
343+
for (String required:requiredProperties) {
344+
if (!subject.has(required)) {
345+
addValidationException(failure(format("required key [%s] not found", required), "required"));
346+
}
347+
}
339348
}
340349

341350
private void testSchemaDependencies(final JSONObject subject) {
@@ -389,18 +398,13 @@ private void testPropertyNames(JSONObject subject) {
389398
if (names == null || names.length == 0) {
390399
return;
391400
}
392-
Collection<ValidationException> failures = Arrays.stream(names)
393-
.map(name -> {
394-
try {
395-
propertyNameSchema.validate(name);
396-
return null;
397-
} catch (ValidationException e) {
398-
return e.prepend(name);
399-
}
400-
})
401-
.filter(Objects::nonNull)
402-
.collect(toList());
403-
validationExceptions.addAll(failures);
401+
for (String name: names) {
402+
try {
403+
propertyNameSchema.validate(name);
404+
} catch (ValidationException e) {
405+
addValidationException(e.prepend(name));
406+
}
407+
}
404408
}
405409
}
406410

@@ -435,21 +439,26 @@ private boolean definesSchemaProperty(String current, final String remaining) {
435439
}
436440

437441
private boolean definesPatternProperty(final String current, final String remaining) {
438-
return patternProperties.keySet()
439-
.stream()
440-
.filter(pattern -> pattern.matcher(current).matches())
441-
.map(pattern -> patternProperties.get(pattern))
442-
.filter(schema -> remaining == null || schema.definesProperty(remaining))
443-
.findAny()
444-
.isPresent();
442+
for (Pattern pattern: patternProperties.keySet()) {
443+
if (pattern.matcher(current).matches()) {
444+
if (remaining == null || patternProperties.get(pattern).definesProperty(remaining)) {
445+
return true;
446+
}
447+
}
448+
}
449+
return false;
445450
}
446451

447452
private boolean definesSchemaDependencyProperty(final String field) {
448-
return schemaDependencies.containsKey(field)
449-
|| schemaDependencies.values().stream()
450-
.filter(schema -> schema.definesProperty(field))
451-
.findAny()
452-
.isPresent();
453+
if (schemaDependencies.containsKey(field)) {
454+
return true;
455+
}
456+
for (Schema schema: schemaDependencies.values()) {
457+
if (schema.definesProperty(field)) {
458+
return true;
459+
}
460+
}
461+
return false;
453462
}
454463

455464
private String unescape(final String value) {

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,10 @@ public void validate(final Object subject) {
148148
String stringSubject = (String) subject;
149149
testLength(stringSubject);
150150
testPattern(stringSubject);
151-
formatValidator.validate(stringSubject)
152-
.map(failure -> failure(failure, "format"))
153-
.ifPresent(this::addValidationException);
151+
Optional<String> failure = formatValidator.validate(stringSubject);
152+
if (failure.isPresent()) {
153+
addValidationException(failure(failure.get(), "format"));
154+
}
154155
if (null != validationExceptions) {
155156
ValidationException.throwFor(this, validationExceptions);
156157
}

core/src/main/java/org/everit/json/schema/internal/DateTimeFormatValidator.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
package org.everit.json.schema.internal;
22

3+
import com.google.common.collect.ImmutableList;
4+
import org.everit.json.schema.FormatValidator;
5+
36
import java.time.format.DateTimeFormatter;
47
import java.time.format.DateTimeFormatterBuilder;
58
import java.time.format.DateTimeParseException;
69
import java.time.temporal.ChronoField;
710
import java.util.List;
811
import java.util.Optional;
912

10-
import org.everit.json.schema.FormatValidator;
11-
12-
import com.google.common.collect.ImmutableList;
13-
1413
/**
1514
* Implementation of the "date-time" format value.
1615
*/

0 commit comments

Comments
 (0)