[json schema] IJPL-163460 Get rid of vararg in API to avoid accidental excessive arrays copying

(cherry picked from commit 100b1a8277016b3a6f99c26fccb357101a64d3f0)

IJ-CR-147983

GitOrigin-RevId: 70dd14ac5cb2836eff3ad14cf4063d8f319f353f
This commit is contained in:
Nikita Katkov
2024-10-28 15:19:41 +01:00
committed by intellij-monorepo-bot
parent 2503b96301
commit 53af24ec04
9 changed files with 79 additions and 83 deletions

View File

@@ -30,7 +30,7 @@ public abstract class JsonSchemaObject {
public abstract @Nullable Boolean getConstantSchema();
@ApiStatus.Experimental
public abstract boolean hasChildFieldsExcept(@NotNull String @NotNull ... namesToSkip);
public abstract boolean hasChildFieldsExcept(@NotNull List<@NotNull String> namesToSkip);
public abstract @NotNull Iterable<JsonSchemaValidation> getValidations(@Nullable JsonSchemaType type, @NotNull JsonValueAdapter value);
@@ -154,9 +154,9 @@ public abstract class JsonSchemaObject {
public abstract @NotNull Iterator<String> getDefinitionNames();
public abstract @Nullable String readChildNodeValue(@NotNull String @NotNull ... childNodeName);
public abstract @Nullable String readChildNodeValue(@NotNull String childNodeName);
public abstract boolean hasChildNode(@NotNull String @NotNull ... childNodeName);
public abstract boolean hasChildNode(@NotNull String childNodeName);
public abstract @NotNull Iterator<String> getPropertyNames();

View File

@@ -121,12 +121,12 @@ public class JsonSchemaObjectImpl extends JsonSchemaObject {
public final UserDataHolderBase myUserDataHolder = new UserDataHolderBase();
@Override
public @Nullable String readChildNodeValue(@NotNull String @NotNull ... childNodeName) {
public @Nullable String readChildNodeValue(@NotNull String childNodeName) {
return null;
}
@Override
public boolean hasChildNode(@NotNull String @NotNull ... childNodeName) {
public boolean hasChildNode(@NotNull String childNodeName) {
return false;
}
@@ -136,7 +136,7 @@ public class JsonSchemaObjectImpl extends JsonSchemaObject {
}
@Override
public boolean hasChildFieldsExcept(@NotNull String @NotNull ... namesToSkip) {
public boolean hasChildFieldsExcept(@NotNull List<@NotNull String> namesToSkip) {
return false;
}

View File

@@ -2,11 +2,14 @@
package com.jetbrains.jsonSchema.impl.light
import com.jetbrains.jsonSchema.impl.JsonSchemaObject
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
interface JsonSchemaNodePointer<T> {
val rawSchemaNode: T
}
@ApiStatus.Internal
interface JsonSchemaObjectFactory<T, V> where V : JsonSchemaObject, V : JsonSchemaNodePointer<T> {
/**
* @return an instance of schema object backed by physically existing schema node that can be found by a combined json pointer, where
@@ -32,18 +35,18 @@ interface RawJsonSchemaNodeAccessor<T> {
/**
* Resolve raw schema node from the given schema node by the given node's name
*/
fun resolveRelativeNode(node: T, vararg relativeChildPath: String): T?
fun resolveRelativeNode(node: T, relativeChildPath: String? = null): T?
fun hasChildNode(node: T, vararg relativeChildPath: String): Boolean
fun hasChildNode(node: T, relativeChildPath: String): Boolean
fun readTextNodeValue(node: T, vararg relativeChildPath: String): String?
fun readBooleanNodeValue(node: T, vararg relativeChildPath: String): Boolean?
fun readNumberNodeValue(node: T, vararg relativeChildPath: String): Number?
fun readUntypedNodeValueAsText(node: T, vararg relativeChildPath: String): String?
fun readTextNodeValue(node: T, relativeChildPath: String? = null): String?
fun readBooleanNodeValue(node: T, relativeChildPath: String? = null): Boolean?
fun readNumberNodeValue(node: T, relativeChildPath: String? = null): Number?
fun readUntypedNodeValueAsText(node: T, relativeChildPath: String? = null): String?
fun readNodeKeys(node: T, vararg relativeChildPath: String): Sequence<String>?
fun readNodeKeys(node: T, relativeChildPath: String? = null): Sequence<String>?
fun readUntypedNodesCollection(node: T, vararg relativeChildPath: String): Sequence<Any>?
fun readNodeAsMapEntries(node: T, vararg relativeChildPath: String): Sequence<Pair<String, T>>?
fun readNodeAsMultiMapEntries(node: T, vararg relativeChildPath: String): Sequence<Pair<String, List<String>>>?
fun readUntypedNodesCollection(node: T, relativeChildPath: String? = null): Sequence<Any>?
fun readNodeAsMapEntries(node: T, relativeChildPath: String? = null): Sequence<Pair<String, T>>?
fun readNodeAsMultiMapEntries(node: T, relativeChildPath: String? = null): Sequence<Pair<String, List<String>>>?
}

View File

@@ -35,15 +35,15 @@ internal class InheritedJsonSchemaObjectView(
return other.ref
}
override fun readChildNodeValue(vararg childNodeName: String): String? {
override fun readChildNodeValue(childNodeName: String): String? {
return baseIfConditionOrOtherWithArgument(JsonSchemaObject::readChildNodeValue, childNodeName, String?::isNotBlank)
}
override fun hasChildNode(vararg childNodeName: String): Boolean {
return other.hasChildNode(*childNodeName)
override fun hasChildNode(childNodeName: String): Boolean {
return other.hasChildNode(childNodeName)
}
override fun hasChildFieldsExcept(namesToSkip: Array<String>): Boolean {
override fun hasChildFieldsExcept(namesToSkip: List<String>): Boolean {
return booleanOrWithArgument(JsonSchemaObject::hasChildFieldsExcept, namesToSkip)
}

View File

@@ -32,7 +32,7 @@ private const val INVALID_PATTERN_FALLBACK = "__invalid_ij_pattern"
@ApiStatus.Internal
abstract class JsonSchemaObjectBackedByJacksonBase(
override val rawSchemaNode: JsonNode,
private val jsonPointer: String
private val jsonPointer: String,
) : JsonSchemaObjectLegacyAdapter(), JsonSchemaNodePointer<JsonNode> {
abstract override fun getRootSchemaObject(): RootJsonSchemaObjectBackedByJackson
@@ -87,18 +87,18 @@ abstract class JsonSchemaObjectBackedByJacksonBase(
return getRootSchemaObject().rawFile
}
override fun hasChildFieldsExcept(namesToSkip: Array<String>): Boolean {
override fun hasChildFieldsExcept(namesToSkip: List<String>): Boolean {
return JacksonSchemaNodeAccessor.readNodeKeys(rawSchemaNode)
.orEmpty()
.any { it !in namesToSkip }
}
override fun hasChildNode(vararg childNodeName: String): Boolean {
return JacksonSchemaNodeAccessor.hasChildNode(rawSchemaNode, *childNodeName)
override fun hasChildNode(childNodeName: String): Boolean {
return JacksonSchemaNodeAccessor.hasChildNode(rawSchemaNode, childNodeName)
}
override fun readChildNodeValue(vararg childNodeName: String): String? {
return JacksonSchemaNodeAccessor.readUntypedNodeValueAsText(rawSchemaNode, *childNodeName)
override fun readChildNodeValue(childNodeName: String): String? {
return JacksonSchemaNodeAccessor.readUntypedNodeValueAsText(rawSchemaNode, childNodeName)
}
override fun getConstantSchema(): Boolean? {
@@ -474,11 +474,11 @@ abstract class JsonSchemaObjectBackedByJacksonBase(
.toList()
}
private fun createChildMap(vararg childMapName: String): Map<String, JsonSchemaObject>? {
return JacksonSchemaNodeAccessor.readNodeAsMapEntries(rawSchemaNode, *childMapName)
private fun createChildMap(childMapName: String): Map<String, JsonSchemaObject>? {
return JacksonSchemaNodeAccessor.readNodeAsMapEntries(rawSchemaNode, childMapName)
?.mapNotNull { (key, value) ->
if (!value.isObject) return@mapNotNull null
val childObject = createResolvableChild(*childMapName, key) ?: return@mapNotNull null
val childObject = createResolvableChild(childMapName, key) ?: return@mapNotNull null
key to childObject
}?.toMap()
}
@@ -510,16 +510,21 @@ abstract class JsonSchemaObjectBackedByJacksonBase(
}
override fun getLanguageInjection(): String? {
return JacksonSchemaNodeAccessor.readTextNodeValue(rawSchemaNode, X_INTELLIJ_LANGUAGE_INJECTION)
?: JacksonSchemaNodeAccessor.readTextNodeValue(rawSchemaNode, X_INTELLIJ_LANGUAGE_INJECTION, LANGUAGE)
val directChild = JacksonSchemaNodeAccessor.readTextNodeValue(rawSchemaNode, X_INTELLIJ_LANGUAGE_INJECTION)
if (directChild != null) return directChild
val intermediateNode = JacksonSchemaNodeAccessor.resolveRelativeNode(rawSchemaNode, X_INTELLIJ_LANGUAGE_INJECTION) ?: return null
return JacksonSchemaNodeAccessor.readTextNodeValue(intermediateNode, LANGUAGE)
}
override fun getLanguageInjectionPrefix(): String? {
return JacksonSchemaNodeAccessor.readTextNodeValue(rawSchemaNode, X_INTELLIJ_LANGUAGE_INJECTION, PREFIX)
val intermediateNode = JacksonSchemaNodeAccessor.resolveRelativeNode(rawSchemaNode, X_INTELLIJ_LANGUAGE_INJECTION) ?: return null
return JacksonSchemaNodeAccessor.readTextNodeValue(intermediateNode, PREFIX)
}
override fun getLanguageInjectionPostfix(): String? {
return JacksonSchemaNodeAccessor.readTextNodeValue(rawSchemaNode, X_INTELLIJ_LANGUAGE_INJECTION, SUFFIX)
val intermediateNode = JacksonSchemaNodeAccessor.resolveRelativeNode(rawSchemaNode, X_INTELLIJ_LANGUAGE_INJECTION) ?: return null
return JacksonSchemaNodeAccessor.readTextNodeValue(intermediateNode, SUFFIX)
}
override fun isShouldValidateAgainstJSType(): Boolean {

View File

@@ -37,15 +37,15 @@ internal class MergedJsonSchemaObjectView(
return other.ref
}
override fun readChildNodeValue(vararg childNodeName: String): String? {
override fun readChildNodeValue(childNodeName: String): String? {
return baseIfConditionOrOtherWithArgument(JsonSchemaObject::readChildNodeValue, childNodeName, String?::isNotBlank)
}
override fun hasChildNode(vararg childNodeName: String): Boolean {
override fun hasChildNode(childNodeName: String): Boolean {
return booleanOrWithArgument(JsonSchemaObject::hasChildNode, childNodeName)
}
override fun hasChildFieldsExcept(namesToSkip: Array<String>): Boolean {
override fun hasChildFieldsExcept(namesToSkip: List<String>): Boolean {
return booleanOrWithArgument(JsonSchemaObject::hasChildFieldsExcept, namesToSkip)
}

View File

@@ -270,7 +270,7 @@ internal object MissingJsonSchemaObject : JsonSchemaObjectBackedByJacksonBase(Mi
throw UnsupportedOperationException(ERROR_MESSAGE)
}
override fun hasChildFieldsExcept(namesToSkip: Array<String>): Boolean {
override fun hasChildFieldsExcept(namesToSkip: List<String>): Boolean {
throw UnsupportedOperationException(ERROR_MESSAGE)
}

View File

@@ -19,49 +19,43 @@ internal object JacksonSchemaNodeAccessor : RawJsonSchemaNodeAccessor<JsonNode>
return rootNode.at(compiledPointer)?.takeIf { it !is MissingNode }
}
override fun resolveRelativeNode(node: JsonNode, vararg relativeChildPath: String): JsonNode? {
return relativeChildPath.asSequence().map(::escapeForbiddenJsonPointerSymbols).fold(node) { currentNode, childName ->
val childByName = currentNode.get(childName)
if (childByName != null) return@fold childByName
val maybeIndex = childName.toIntOrNull() ?: return@fold MissingNode.getInstance()
return currentNode.get(maybeIndex)
}.takeIf { it !is MissingNode }
override fun resolveRelativeNode(node: JsonNode, relativeChildPath: String?): JsonNode? {
return getExistingChildByNonEmptyPathOrSelf(node, relativeChildPath)
}
override fun hasChildNode(node: JsonNode, vararg relativeChildPath: String): Boolean {
override fun hasChildNode(node: JsonNode, relativeChildPath: String): Boolean {
if (!node.isObject) return false
return !getChild(node, *relativeChildPath).isMissingNode
return !getExistingChildByNonEmptyPathOrSelf(node, relativeChildPath).isMissingNode
}
override fun readUntypedNodeValueAsText(node: JsonNode, vararg relativeChildPath: String): String? {
override fun readUntypedNodeValueAsText(node: JsonNode, relativeChildPath: String?): String? {
if (!node.isObject) return null
return getChild(node, *relativeChildPath)
return getExistingChildByNonEmptyPathOrSelf(node, relativeChildPath)
.takeIf { it !is MissingNode }
?.toPrettyString()
}
override fun readTextNodeValue(node: JsonNode, vararg relativeChildPath: String): String? {
override fun readTextNodeValue(node: JsonNode, relativeChildPath: String?): String? {
if (!node.isObject) return null
val maybeString = getChild(node, *relativeChildPath)
val maybeString = getExistingChildByNonEmptyPathOrSelf(node, relativeChildPath)
return if (maybeString.isTextual)
maybeString.asText()
else
null
}
override fun readBooleanNodeValue(node: JsonNode, vararg relativeChildPath: String): Boolean? {
if (!(node.isObject || (node.isBoolean && relativeChildPath.isEmpty()))) return null
val maybeBoolean = getChild(node, *relativeChildPath)
override fun readBooleanNodeValue(node: JsonNode, relativeChildPath: String?): Boolean? {
if (!(node.isObject || (node.isBoolean && relativeChildPath == null))) return null
val maybeBoolean = if (relativeChildPath == null) node else getExistingChildByNonEmptyPathOrSelf(node, relativeChildPath)
return if (maybeBoolean.isBoolean)
maybeBoolean.asBoolean()
else
null
}
override fun readNumberNodeValue(node: JsonNode, vararg relativeChildPath: String): Number? {
override fun readNumberNodeValue(node: JsonNode, relativeChildPath: String?): Number? {
if (!node.isObject) return null
val maybeNumber = getChild(node, *relativeChildPath)
val maybeNumber = getExistingChildByNonEmptyPathOrSelf(node, relativeChildPath)
return when {
maybeNumber.isInt -> maybeNumber.asInt()
maybeNumber.isDouble -> maybeNumber.asDouble()
@@ -70,45 +64,43 @@ internal object JacksonSchemaNodeAccessor : RawJsonSchemaNodeAccessor<JsonNode>
}
}
override fun readUntypedNodesCollection(node: JsonNode, vararg relativeChildPath: String): Sequence<Any>? {
return getChildArrayItems(node, *relativeChildPath)
override fun readUntypedNodesCollection(node: JsonNode, relativeChildPath: String?): Sequence<Any>? {
return getChildArrayItems(node, relativeChildPath)
?.mapNotNull(JacksonSchemaNodeAccessor::readAnything)
}
override fun readNodeAsMapEntries(node: JsonNode, vararg relativeChildPath: String): Sequence<Pair<String, JsonNode>>? {
override fun readNodeAsMapEntries(node: JsonNode, relativeChildPath: String?): Sequence<Pair<String, JsonNode>>? {
if (!node.isObject) return null
return getChild(node, *relativeChildPath)
return getExistingChildByNonEmptyPathOrSelf(node, relativeChildPath)
.takeIf { it.isObject }
?.fields()
?.asSequence()
?.map { it.key to it.value }
}
override fun readNodeAsMultiMapEntries(node: JsonNode, vararg relativeChildPath: String): Sequence<Pair<String, List<String>>>? {
return readNodeAsMapEntries(node, *relativeChildPath)
override fun readNodeAsMultiMapEntries(node: JsonNode, relativeChildPath: String?): Sequence<Pair<String, List<String>>>? {
return readNodeAsMapEntries(node, relativeChildPath)
?.mapNotNull { (stringKey, arrayValue) ->
if (!arrayValue.isArray) return@mapNotNull null
stringKey to arrayValue.elements().asSequence().mapNotNull { if (it.isTextual) it.asText() else null }.toList()
}
}
override fun readNodeKeys(node: JsonNode, vararg relativeChildPath: String): Sequence<String>? {
val expandedNode = if (relativeChildPath.isEmpty())
node
else
getChild(node, *relativeChildPath)
override fun readNodeKeys(node: JsonNode, relativeChildPath: String?): Sequence<String>? {
val expandedNode = getExistingChildByNonEmptyPathOrSelf(node, relativeChildPath)
return expandedNode.fieldNames().takeIf(Iterator<String>::hasNext)?.asSequence()
}
private fun getChild(node: JsonNode, vararg relativePath: String): JsonNode {
return relativePath.fold(node) { parentNode, childName ->
parentNode.get(childName) ?: return MissingNode.getInstance()
}
private fun getExistingChildByNonEmptyPathOrSelf(node: JsonNode, directChildName: String? = null): JsonNode {
return if (directChildName == null)
node
else
node.get(directChildName) ?: MissingNode.getInstance()
}
private fun getChildArrayItems(node: JsonNode, vararg name: String): Sequence<JsonNode>? {
private fun getChildArrayItems(node: JsonNode, name: String?): Sequence<JsonNode>? {
if (!node.isObject) return null
return getChild(node, *name)
return getExistingChildByNonEmptyPathOrSelf(node, name)
.takeIf { it.isArray }
?.asSafely<ArrayNode>()
?.elements()

View File

@@ -110,18 +110,11 @@ interface JsonSchemaInterpretationStrategy {
|| baseSchema.rawFile != null && baseSchema.rawFile == childSchema.rawFile
}
private fun isNodeWithDifferentPointers(
baseSchema: JsonSchemaObject,
childSchema: JsonSchemaObject,
): Boolean {
return baseSchema.pointer != childSchema.pointer
}
private fun isApplicatorBranchWithNonEmptyParent(
baseSchema: JsonSchemaObject,
childSchema: JsonSchemaObject,
): Boolean {
if (!baseSchema.hasChildFieldsExcept(ONE_OF, ALL_OF, ANY_OF)) return false
if (!baseSchema.hasChildFieldsExcept(APPLICATOR_MARKERS)) return false
return sequence {
yieldAll(baseSchema.oneOf.orEmpty())
@@ -134,10 +127,13 @@ interface JsonSchemaInterpretationStrategy {
baseSchema: JsonSchemaObject,
childSchema: JsonSchemaObject,
): Boolean {
if (!baseSchema.hasChildFieldsExcept(IF, THEN, ELSE)) return false
if (!baseSchema.hasChildFieldsExcept(IF_ELSE_MARKERS)) return false
return baseSchema.ifThenElse?.any { condition ->
condition.then == childSchema || condition.`else` == childSchema
} ?: false
}
}
}
private val IF_ELSE_MARKERS = listOf(IF, THEN, ELSE)
private val APPLICATOR_MARKERS = listOf(ONE_OF, ALL_OF, ANY_OF)