[JavaScript Debugger, script debugger] WEB-70558 support reading of array of object or single object value

In r1359167 Google introduced a breaking change: the type of the `Debugger.scriptParsed.debugSymbol` field was changed from `Debugger.DebugSymbols` to `array[Debugger.DebugSymbols]`.

To provide backward compatibility with old browsers, this fix introduces a new reader method `readObjectArrayOrSingleObject` which used if some object property marked with `JsonArray` annotation.


(cherry picked from commit 7847455a7c59cf7c7af3de9b991f2b6ca51928e7)

IJ-CR-150473

GitOrigin-RevId: ae6d2a42490bdcbf867d2727ce8207695d6ee3c5
This commit is contained in:
Shumaf Lovpache
2024-11-28 17:54:59 +02:00
committed by intellij-monorepo-bot
parent 714c16f197
commit dc39c7d32d
10 changed files with 44 additions and 19 deletions

View File

@@ -22,7 +22,14 @@ import org.jetbrains.annotations.ApiStatus
annotation class JsonField( annotation class JsonField(
val allowAnyPrimitiveValue: Boolean = false, // read any primitive value as String (true as true, number as string - don't try to parse) val allowAnyPrimitiveValue: Boolean = false, // read any primitive value as String (true as true, number as string - don't try to parse)
val allowAnyPrimitiveValueAndMap: Boolean = false, val allowAnyPrimitiveValueAndMap: Boolean = false,
val primitiveValue: String = "") val primitiveValue: String = "",
)
@ApiStatus.Internal
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION)
annotation class JsonArray(
val allowSingleObject: Boolean = false, // when schema changes from {...} to [{...}] we need to handle both cases
)
@ApiStatus.Internal @ApiStatus.Internal
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)

View File

@@ -134,6 +134,15 @@ public final class JsonReaders {
} }
} }
public static <T> List<T> readObjectArrayOrSingleObject(@NotNull JsonReaderEx reader, @NotNull ObjectFactory<? extends T> factory) {
if (reader.peek() == JsonToken.BEGIN_OBJECT) {
return Collections.singletonList(factory.read(reader));
}
else {
return readObjectArray(reader, factory);
}
}
public static <T> List<T> readObjectArray(@NotNull JsonReaderEx reader, @NotNull ObjectFactory<? extends T> factory) { public static <T> List<T> readObjectArray(@NotNull JsonReaderEx reader, @NotNull ObjectFactory<? extends T> factory) {
if (reader.peek() == JsonToken.NULL) { if (reader.peek() == JsonToken.NULL) {
reader.skipValue(); reader.skipValue();

View File

@@ -1,6 +1,6 @@
package org.jetbrains.protocolReader package org.jetbrains.protocolReader
internal class ArrayReader(private val componentParser: ValueReader, private val isList: Boolean) : ValueReader() { internal class ArrayReader(private val componentParser: ValueReader, private val isList: Boolean, private val allowSingleObject: Boolean) : ValueReader() {
override fun appendFinishedValueTypeName(out: TextOutput) { override fun appendFinishedValueTypeName(out: TextOutput) {
if (isList) { if (isList) {
out.append("List<") out.append("List<")
@@ -17,18 +17,24 @@ internal class ArrayReader(private val componentParser: ValueReader, private val
out.append('>') out.append('>')
} }
override fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) { override fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, allowSingleValue: Boolean, out: TextOutput) {
beginReadCall("ObjectArray", subtyping, out) val readPostfix = if (allowSingleValue) {
"ObjectArrayOrSingleObject"
}
else {
"ObjectArray"
}
beginReadCall(readPostfix, subtyping, out)
out out
.comma() .comma()
// the trick with shadowing isn't very good, but at least it's simple // the trick with shadowing isn't very good, but at least it's simple
.append("WrapperFactory { $READER_NAME -> ") .append("WrapperFactory { $READER_NAME -> ")
componentParser.writeArrayReadCode(scope, subtyping, out) componentParser.writeArrayReadCode(scope, subtyping, allowSingleObject, out)
out.append("}") out.append("}")
out.append(')') out.append(')')
} }
override fun writeReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) { override fun writeReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) {
componentParser.writeArrayReadCode(scope, subtyping, out) componentParser.writeArrayReadCode(scope, subtyping, allowSingleObject, out)
} }
} }

View File

@@ -5,7 +5,7 @@ internal class EnumReader(private val enumClass: Class<Enum<*>>) : ValueReader()
out.append(enumClass.canonicalName) out.append(enumClass.canonicalName)
} }
override fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) { override fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, allowSingleValue: Boolean, out: TextOutput) {
beginReadCall("EnumArray", subtyping, out) beginReadCall("EnumArray", subtyping, out)
out.comma().append(enumClass.canonicalName).append("::class.java)") out.comma().append(enumClass.canonicalName).append("::class.java)")
} }

View File

@@ -2,10 +2,7 @@
package org.jetbrains.protocolReader package org.jetbrains.protocolReader
import org.jetbrains.io.JsonReaderEx import org.jetbrains.io.JsonReaderEx
import org.jetbrains.jsonProtocol.JsonField import org.jetbrains.jsonProtocol.*
import org.jetbrains.jsonProtocol.JsonSubtype
import org.jetbrains.jsonProtocol.Optional
import org.jetbrains.jsonProtocol.StringIntPair
import java.lang.reflect.Method import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type import java.lang.reflect.Type
@@ -157,7 +154,7 @@ internal class InterfaceReader(private val typeToTypeHandler: LinkedHashMap<Clas
type == Any::class.java -> RAW_STRING_OR_MAP_PARSER type == Any::class.java -> RAW_STRING_OR_MAP_PARSER
type == JsonReaderEx::class.java -> JSON_PARSER type == JsonReaderEx::class.java -> JSON_PARSER
type == StringIntPair::class.java -> STRING_INT_PAIR_PARSER type == StringIntPair::class.java -> STRING_INT_PAIR_PARSER
type.isArray -> ArrayReader(getFieldTypeParser(null, type.componentType, false, null), false) type.isArray -> ArrayReader(getFieldTypeParser(null, type.componentType, false, null), false, member?.annotation<JsonArray>()?.allowSingleObject == true)
type.isEnum -> EnumReader(type as Class<Enum<*>>) type.isEnum -> EnumReader(type as Class<Enum<*>>)
else -> { else -> {
val ref = getTypeRef(type) val ref = getTypeRef(type)
@@ -176,7 +173,7 @@ internal class InterfaceReader(private val typeToTypeHandler: LinkedHashMap<Clas
} }
} }
val componentParser = getFieldTypeParser(null, argumentType, false, method) val componentParser = getFieldTypeParser(null, argumentType, false, method)
return if (isList) ArrayReader(componentParser, true) else MapReader(componentParser) return if (isList) ArrayReader(componentParser, true, member?.annotation<JsonArray>()?.allowSingleObject == true) else MapReader(componentParser)
} }
else { else {
throw UnsupportedOperationException("Method return type $type (generic) not supported") throw UnsupportedOperationException("Method return type $type (generic) not supported")

View File

@@ -20,7 +20,7 @@ internal class MapReader(private val componentParser: ValueReader) : ValueReader
out.append(')') out.append(')')
} }
override fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) { override fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, allowSingleValue: Boolean, out: TextOutput) {
beginReadCall("ObjectArray", subtyping, out) beginReadCall("ObjectArray", subtyping, out)
out.comma().append("mapFactory(") out.comma().append("mapFactory(")
if (componentParser is ObjectValueReader) { if (componentParser is ObjectValueReader) {

View File

@@ -29,8 +29,14 @@ internal class ObjectValueReader(val type: TypeRef<*>, private val isSubtyping:
out.append(')') out.append(')')
} }
override fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) { override fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, allowSingleValue: Boolean, out: TextOutput) {
beginReadCall("ObjectArray", subtyping, out) val readPostfix = if (allowSingleValue) {
"ObjectArrayOrSingleObject"
}
else {
"ObjectArray"
}
beginReadCall(readPostfix, subtyping, out)
writeFactoryArgument(scope, out) writeFactoryArgument(scope, out)
out.append(')') out.append(')')
} }

View File

@@ -32,7 +32,7 @@ internal open class PrimitiveValueReader(val className: String, val defaultValue
out.append(className) out.append(className)
} }
override fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) { override fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, allowSingleValue: Boolean, out: TextOutput) {
if (readPostfix == "String") { if (readPostfix == "String") {
out.append("nextList") out.append("nextList")
} }

View File

@@ -8,7 +8,7 @@ internal class StringIntPairValueReader : ValueReader() {
override fun writeReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) { override fun writeReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) {
} }
override fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) { override fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, allowSingleValue: Boolean, out: TextOutput) {
out.append("read").append("IntStringPairs").append('(') out.append("read").append("IntStringPairs").append('(')
addReaderParameter(subtyping, out) addReaderParameter(subtyping, out)
out.append(')') out.append(')')

View File

@@ -24,7 +24,7 @@ internal abstract class ValueReader {
abstract fun writeReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) abstract fun writeReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput)
open fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, out: TextOutput) { open fun writeArrayReadCode(scope: ClassScope, subtyping: Boolean, allowSingleValue: Boolean, out: TextOutput) {
throw UnsupportedOperationException() throw UnsupportedOperationException()
} }