From b206baa186e804491be76b7928097deae85daae7 Mon Sep 17 00:00:00 2001 From: leadpony Date: Sat, 1 Aug 2020 18:27:07 +0900 Subject: [PATCH] Change RefKeyword into applicator --- .../api/keyword/ApplicatorKeyword.java | 10 +++ .../justify/api/keyword/RefKeyword.java | 26 +++++++- .../internal/keyword/core/RecursiveRef.java | 5 ++ .../justify/internal/keyword/core/Ref.java | 37 ++++++++++- .../schema/io/InfiniteLoopDetector.java | 65 +++++++++---------- 5 files changed, 106 insertions(+), 37 deletions(-) diff --git a/justify/src/main/java/org/leadpony/justify/api/keyword/ApplicatorKeyword.java b/justify/src/main/java/org/leadpony/justify/api/keyword/ApplicatorKeyword.java index f26b186d..1307e02f 100644 --- a/justify/src/main/java/org/leadpony/justify/api/keyword/ApplicatorKeyword.java +++ b/justify/src/main/java/org/leadpony/justify/api/keyword/ApplicatorKeyword.java @@ -54,4 +54,14 @@ default boolean canEvaluate() { * @return the location. */ ApplicableLocation getApplicableLocation(); + + /** + * Returns whether this keyword is an in-place applicator or not. + * + * @return {@code true} if this keyword is an in-place applicator, {@code false} + * otherwise. + */ + default boolean isInPlace() { + return getApplicableLocation() == ApplicableLocation.CURRENT; + } } diff --git a/justify/src/main/java/org/leadpony/justify/api/keyword/RefKeyword.java b/justify/src/main/java/org/leadpony/justify/api/keyword/RefKeyword.java index df97aed9..0569b4bb 100644 --- a/justify/src/main/java/org/leadpony/justify/api/keyword/RefKeyword.java +++ b/justify/src/main/java/org/leadpony/justify/api/keyword/RefKeyword.java @@ -25,7 +25,31 @@ * @author leadpony * @since 4.0 */ -public interface RefKeyword extends EvaluationKeyword, SimpleValueKeyword { +public interface RefKeyword extends ApplicatorKeyword, SimpleValueKeyword { + + /** + * {@inheritDoc}} + */ + @Override + default ApplicableLocation getApplicableLocation() { + return ApplicableLocation.CURRENT; + } + + /** + * {@inheritDoc}} + */ + @Override + default boolean isInPlace() { + return true; + } + + /** + * Returns whether this keyword is direct reference or not. + * + * @return {@code true} if this keyword is direct reference, {@code false} + * otherwise. + */ + boolean isDirect(); /** * Returns the JSON schema reference. diff --git a/justify/src/main/java/org/leadpony/justify/internal/keyword/core/RecursiveRef.java b/justify/src/main/java/org/leadpony/justify/internal/keyword/core/RecursiveRef.java index a250e530..39b6ec8d 100644 --- a/justify/src/main/java/org/leadpony/justify/internal/keyword/core/RecursiveRef.java +++ b/justify/src/main/java/org/leadpony/justify/internal/keyword/core/RecursiveRef.java @@ -70,6 +70,11 @@ public KeywordType getType() { return TYPE; } + @Override + public boolean isDirect() { + return false; + } + @Override protected JsonSchema findtTargetSchema(Evaluator parent, JsonSchemaReference reference) { final JsonSchema direct = reference.getTargetSchema(); diff --git a/justify/src/main/java/org/leadpony/justify/internal/keyword/core/Ref.java b/justify/src/main/java/org/leadpony/justify/internal/keyword/core/Ref.java index 2ac5a238..e1c2a152 100644 --- a/justify/src/main/java/org/leadpony/justify/internal/keyword/core/Ref.java +++ b/justify/src/main/java/org/leadpony/justify/internal/keyword/core/Ref.java @@ -16,6 +16,10 @@ package org.leadpony.justify.internal.keyword.core; import java.net.URI; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; import jakarta.json.JsonValue; @@ -75,6 +79,32 @@ public KeywordType getType() { return TYPE; } + @Override + public boolean containsSchemas() { + return true; + } + + @Override + public Map getSchemasAsMap() { + Map schemaMap = new HashMap<>(); + schemaMap.put("", getTargetSchema()); + return schemaMap; + } + + @Override + public Stream getSchemasAsStream() { + return Stream.of(getTargetSchema()); + } + + @Override + public Optional findSchema(String jsonPointer) { + if (jsonPointer.isEmpty()) { + return Optional.of(getTargetSchema()); + } else { + return Optional.empty(); + } + } + @Override public boolean canEvaluate() { return true; @@ -99,12 +129,17 @@ public URI value() { return reference.getTargetId(); } + @Override + public boolean isDirect() { + return true; + } + @Override public JsonSchemaReference getSchemaReference() { return reference; } protected JsonSchema findtTargetSchema(Evaluator parent, JsonSchemaReference reference) { - return reference.getTargetSchema(); + return getTargetSchema(); } } diff --git a/justify/src/main/java/org/leadpony/justify/internal/schema/io/InfiniteLoopDetector.java b/justify/src/main/java/org/leadpony/justify/internal/schema/io/InfiniteLoopDetector.java index d80ff9c3..271a912e 100644 --- a/justify/src/main/java/org/leadpony/justify/internal/schema/io/InfiniteLoopDetector.java +++ b/justify/src/main/java/org/leadpony/justify/internal/schema/io/InfiniteLoopDetector.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2019 the Justify authors. + * Copyright 2018, 2020 the Justify authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,15 @@ */ package org.leadpony.justify.internal.schema.io; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; +import java.util.Collection; import java.util.Set; import org.leadpony.justify.api.JsonSchema; +import org.leadpony.justify.api.keyword.ApplicatorKeyword; import org.leadpony.justify.api.keyword.JsonSchemaReference; import org.leadpony.justify.api.keyword.Keyword; import org.leadpony.justify.api.keyword.RefKeyword; +import org.leadpony.justify.internal.base.Sets; /** * A detector of infinite recursive looping starting from a schema reference. @@ -32,47 +32,42 @@ */ class InfiniteLoopDetector { - private static final String REFERENCE_KEYWORD = "$ref"; - - private final Set checkPoints = new HashSet<>(); + private final Set visited = Sets.newIdentitySet(); boolean detectInfiniteLoop(JsonSchemaReference ref) { - try { - return detectLoopFrom(ref); - } finally { - checkPoints.clear(); - } + assert visited.isEmpty(); + return detectLoopFrom(ref.getTargetSchema()); } private boolean detectLoopFrom(JsonSchema schema) { - - Map keywords = schema.getKeywordsAsMap(); - if (keywords.containsKey(REFERENCE_KEYWORD)) { - Keyword keyword = keywords.get(REFERENCE_KEYWORD); - if (keyword instanceof RefKeyword) { - RefKeyword ref = (RefKeyword) keyword; - if (detectLoopFrom(ref.getSchemaReference())) { - return true; - } - } + if (visited.contains(schema)) { + return true; } - Iterator it = schema.getInPlaceSubschemas().iterator(); - while (it.hasNext()) { - if (detectLoopFrom(it.next())) { - return true; - } + try { + visited.add(schema); + return walkSubschemas(schema); + } finally { + visited.remove(schema); } - return false; } - private boolean detectLoopFrom(JsonSchemaReference ref) { - if (checkPoints.contains(ref)) { - return true; + private boolean walkSubschemas(JsonSchema schema) { + Collection keywords = schema.getKeywordsAsMap().values(); + return keywords.stream() + .filter(k -> k instanceof ApplicatorKeyword) + .map(k -> (ApplicatorKeyword) k) + .filter(InfiniteLoopDetector::isInPlaceApplicator) + .flatMap(Keyword::getSchemasAsStream) + .anyMatch(this::detectLoopFrom); + } + + private static boolean isInPlaceApplicator(ApplicatorKeyword keyword) { + if (keyword instanceof RefKeyword) { + RefKeyword ref = (RefKeyword) keyword; + return ref.isDirect(); + } else { + return keyword.isInPlace(); } - checkPoints.add(ref); - boolean result = detectLoopFrom(ref.getTargetSchema()); - checkPoints.remove(ref); - return result; } }