Skip to content

Commit

Permalink
Consider anonymous classes; still need a bit of to do
Browse files Browse the repository at this point in the history
  • Loading branch information
nikolay-egorov committed Jul 23, 2021
1 parent 0e78ed1 commit 434f8e4
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ data class SerializedVariablesState(
val fieldDescriptor: MutableMap<String, SerializedVariablesState?> = mutableMapOf()
}


@Serializable
class EvaluatedSnippetMetadata(
val newClasspath: Classpath = emptyList(),
Expand Down
146 changes: 112 additions & 34 deletions src/main/kotlin/org/jetbrains/kotlinx/jupyter/serializationUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package org.jetbrains.kotlinx.jupyter

import org.jetbrains.kotlinx.jupyter.api.VariableState
import org.jetbrains.kotlinx.jupyter.compiler.util.SerializedVariablesState
import java.lang.reflect.Field
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
import kotlin.reflect.KTypeParameter
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.jvm.isAccessible
Expand All @@ -15,8 +17,33 @@ typealias PropertiesData = Collection<KProperty1<out Any, *>>

data class ProcessedSerializedVarsState(
val serializedVariablesState: SerializedVariablesState,
val propertiesData: PropertiesData?
)
val propertiesData: PropertiesData?,
val jvmOnlyFields: Array<Field>? = null
) {
// autogenerated
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as ProcessedSerializedVarsState

if (serializedVariablesState != other.serializedVariablesState) return false
if (propertiesData != other.propertiesData) return false
if (jvmOnlyFields != null) {
if (other.jvmOnlyFields == null) return false
if (!jvmOnlyFields.contentEquals(other.jvmOnlyFields)) return false
} else if (other.jvmOnlyFields != null) return false

return true
}

override fun hashCode(): Int {
var result = serializedVariablesState.hashCode()
result = 31 * result + (propertiesData?.hashCode() ?: 0)
result = 31 * result + (jvmOnlyFields?.contentHashCode() ?: 0)
return result
}
}

data class ProcessedDescriptorsState(
// perhaps, better tp make SerializedVariablesState -> PropertiesData?
Expand All @@ -41,7 +68,6 @@ class VariablesSerializer(private val serializationStep: Int = 2, private val se
*/
private val computedDescriptorsPerCell: MutableMap<Int, ProcessedDescriptorsState> = mutableMapOf()


fun serializeVariables(cellId: Int, variablesState: Map<String, VariableState>): Map<String, SerializedVariablesState> {
if (seenObjectsPerCell.containsKey(cellId)) {
seenObjectsPerCell[cellId]!!.clear()
Expand All @@ -62,12 +88,17 @@ class VariablesSerializer(private val serializationStep: Int = 2, private val se
* @param evaluatedDescriptorsState - origin variable state to get value from
* @param serializedVariablesState - current state of recursive state to go further
*/
private fun updateVariableState(cellId: Int, propertyName: String, evaluatedDescriptorsState: ProcessedDescriptorsState,
serializedVariablesState: SerializedVariablesState): SerializedVariablesState {
private fun updateVariableState(
cellId: Int,
propertyName: String,
evaluatedDescriptorsState: ProcessedDescriptorsState,
serializedVariablesState: SerializedVariablesState
): SerializedVariablesState {
// TODO: consider anonymous class as well and fix bug with state
val value = evaluatedDescriptorsState.instancesPerState[serializedVariablesState]
val propertiesData = evaluatedDescriptorsState.processedSerializedVarsState[serializedVariablesState]
if (propertiesData == null && value != null && value::class.java.isArray) {
return serializeVariableState(cellId, propertyName, null, value, false)
if (propertiesData == null && value != null && (value::class.java.isArray || value::class.java.isMemberClass)) {
return serializeVariableState(cellId, propertyName, propertiesData, value, false)
}
val property = propertiesData?.firstOrNull {
it.name == propertyName
Expand All @@ -76,14 +107,22 @@ class VariablesSerializer(private val serializationStep: Int = 2, private val se
return serializeVariableState(cellId, propertyName, property, value, false)
}


fun serializeVariableState(cellId: Int, name: String?, variableState: VariableState?, isOverride: Boolean = true): SerializedVariablesState {
if (variableState == null || name == null) return SerializedVariablesState()
return serializeVariableState(cellId, name, variableState.property, variableState.value, isOverride)
}

fun serializeVariableState(cellId: Int, name: String, property: KProperty<*>?, value: Any?, isOverride: Boolean = true): SerializedVariablesState {
val processedData = createSerializeVariableState(name, property, value)
fun serializeVariableState(cellId: Int, name: String, property: Field?, value: Any?, isOverride: Boolean = true): SerializedVariablesState {
val processedData = createSerializeVariableState(name, getSimpleTypeNameFrom(property, value), value)
return doActualSerialization(cellId, processedData, value, isOverride)
}

fun serializeVariableState(cellId: Int, name: String, property: KProperty<*>, value: Any?, isOverride: Boolean = true): SerializedVariablesState {
val processedData = createSerializeVariableState(name, getSimpleTypeNameFrom(property, value), value)
return doActualSerialization(cellId, processedData, value, isOverride)
}

private fun doActualSerialization(cellId: Int, processedData: ProcessedSerializedVarsState, value: Any?, isOverride: Boolean = true): SerializedVariablesState {
val serializedVersion = processedData.serializedVariablesState

seenObjectsPerCell.putIfAbsent(cellId, mutableMapOf())
Expand All @@ -103,8 +142,7 @@ class VariablesSerializer(private val serializationStep: Int = 2, private val se
return processedData.serializedVariablesState
}


private fun iterateThroughContainerMembers(cellId: Int, callInstance: Any?, descriptor: MutableFieldDescriptor, properties: PropertiesData?, currentDepth: Int = 0): Unit {
private fun iterateThroughContainerMembers(cellId: Int, callInstance: Any?, descriptor: MutableFieldDescriptor, properties: PropertiesData?, currentDepth: Int = 0) {
if (properties == null || callInstance == null || currentDepth >= serializationStep) return

val serializedIteration = mutableMapOf<String, ProcessedSerializedVarsState>()
Expand All @@ -123,7 +161,7 @@ class VariablesSerializer(private val serializationStep: Int = 2, private val se
val value = tryGetValueFromProperty(it, callInstance)

if (!seenObjectsPerCell!!.containsKey(value)) {
serializedIteration[name] = createSerializeVariableState(name, it, value)
serializedIteration[name] = createSerializeVariableState(name, getSimpleTypeNameFrom(it, value), value)
descriptor[name] = serializedIteration[name]!!.serializedVariablesState
}
if (descriptor[name] != null) {
Expand Down Expand Up @@ -155,61 +193,104 @@ class VariablesSerializer(private val serializationStep: Int = 2, private val se
isArrayType -> {
callInstance
}
else -> { null }
else -> {
null
}
}
if (isArrayType) {
if (callInstance is List<*>) {
callInstance.forEach { arrayElem ->
iterateThroughContainerMembers(cellId, arrayElem, serializedVariablesState.fieldDescriptor,
it.value.propertiesData, currentDepth + 1)
iterateThroughContainerMembers(
cellId,
arrayElem,
serializedVariablesState.fieldDescriptor,
it.value.propertiesData,
currentDepth + 1
)
}
} else {
callInstance as Array<*>
callInstance.forEach { arrayElem ->
iterateThroughContainerMembers(cellId, arrayElem, serializedVariablesState.fieldDescriptor,
it.value.propertiesData, currentDepth + 1)
iterateThroughContainerMembers(
cellId,
arrayElem,
serializedVariablesState.fieldDescriptor,
it.value.propertiesData,
currentDepth + 1
)
}
}

return@forEach
}
iterateThroughContainerMembers(cellId, neededCallInstance, serializedVariablesState.fieldDescriptor,
it.value.propertiesData, currentDepth + 1)

// update state with JVMFields
it.value.jvmOnlyFields?.forEach { field ->
serializedVariablesState.fieldDescriptor[field.name] = serializeVariableState(cellId, field.name, field, neededCallInstance)
instancesPerState[serializedVariablesState] = neededCallInstance
}
iterateThroughContainerMembers(
cellId,
neededCallInstance,
serializedVariablesState.fieldDescriptor,
it.value.propertiesData,
currentDepth + 1
)
}
}
}

private fun getSimpleTypeNameFrom(property: Field?, value: Any?): String? {
return if (property != null) {
val returnType = property.type
returnType.simpleName
} else {
value?.toString()
}
}

private fun createSerializeVariableState(name: String, property: KProperty<*>?, value: Any?): ProcessedSerializedVarsState {
val simpleName = if (property != null) {
private fun getSimpleTypeNameFrom(property: KProperty<*>?, value: Any?): String? {
return if (property != null) {
val returnType = property.returnType
val classifier = returnType.classifier as KClass<*>
classifier.simpleName
val classifier = returnType.classifier
if (classifier is KTypeParameter) {
classifier.name
} else {
(classifier as KClass<*>).simpleName
}
} else {
value?.toString()
}
}

private fun createSerializeVariableState(name: String, simpleTypeName: String?, value: Any?): ProcessedSerializedVarsState {
// make it exception-safe
val membersProperties = try {
if (value != null) value::class.declaredMemberProperties else null
} catch (e: Throwable) {
null
}
val isContainer = if (membersProperties != null) (membersProperties.size > 1 || value!!::class.java.isArray) else false
val type = if (value!= null && value::class.java.isArray) {
val javaClass = value?.javaClass
val jvmFields = if (javaClass != null && javaClass.isMemberClass) {
javaClass.declaredFields
} else { null }

val isContainer = if (membersProperties != null) (
membersProperties.isNotEmpty() || value!!::class.java.isArray || (javaClass != null && javaClass.isMemberClass)
) else false
val type = if (value != null && value::class.java.isArray) {
"Array"
} else if (isContainer && value is List<*>) {
"SingletonList"
} else {
simpleName.toString()
simpleTypeName.toString()
}

val serializedVariablesState = SerializedVariablesState(name, type, getProperString(value), isContainer)

return ProcessedSerializedVarsState(serializedVariablesState, membersProperties)
return ProcessedSerializedVarsState(serializedVariablesState, membersProperties, jvmFields)
}


private fun tryGetValueFromProperty(property: KProperty1<Any, *>, callInstance: Any): Any? {
// some fields may be optimized out like array size. Thus, calling it.isAccessible would return error
val canAccess = try {
Expand Down Expand Up @@ -243,13 +324,10 @@ class VariablesSerializer(private val serializationStep: Int = 2, private val se
false
}
}

}

// TODO: maybe think of considering the depth?
fun getProperString(value: Any?): String {

fun print(builder: StringBuilder, containerSize:Int, index: Int, value: Any?): Unit {
fun print(builder: StringBuilder, containerSize: Int, index: Int, value: Any?) {
if (index != containerSize - 1) {
builder.append(value, ", ")
} else {
Expand Down Expand Up @@ -296,4 +374,4 @@ fun getProperString(value: Any?): String {
}

return value.toString()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,6 @@ class ReplVarsTest : AbstractSingleReplTest() {
}
}


class ReplVarsSerializationTest : AbstractSingleReplTest() {
override val repl = makeSimpleRepl()

Expand Down Expand Up @@ -805,7 +804,7 @@ class ReplVarsSerializationTest : AbstractSingleReplTest() {
val actualContainer = listDescriptors.entries.first().value!!
assertEquals(2, actualContainer.fieldDescriptor.size)
assertTrue(actualContainer.isContainer)
assertEquals(listOf(1,2,3,4).toString().substring(1, actualContainer.value!!.length + 1), actualContainer.value)
assertEquals(listOf(1, 2, 3, 4).toString().substring(1, actualContainer.value!!.length + 1), actualContainer.value)
}

@Test
Expand Down Expand Up @@ -842,6 +841,34 @@ class ReplVarsSerializationTest : AbstractSingleReplTest() {
}
}

@Test
fun cyclicReferenceTest() {
val res = eval(
"""
class C {
inner class Inner;
val i = Inner()
val counter = 0
}
val c = C()
""".trimIndent(),
jupyterId = 1
)
val varsData = res.metadata.evaluatedVariablesState
assertEquals(1, varsData.size)
assertTrue(varsData.containsKey("c"))

val serializedState = varsData["c"]!!
assertTrue(serializedState.isContainer)
val descriptor = serializedState.fieldDescriptor
assertEquals(2, descriptor.size)
assertEquals("0", descriptor["counter"]!!.value)

val serializer = repl.variablesSerializer

val newData = serializer.doIncrementalSerialization(0, descriptor["i"]!!.name, descriptor["i"]!!)
val a = 1
}

@Test
fun incrementalUpdateTest() {
Expand Down Expand Up @@ -882,7 +909,5 @@ class ReplVarsSerializationTest : AbstractSingleReplTest() {
assertTrue(state.isContainer)
assertEquals("${values++}", state.value)
}

}

}
}

0 comments on commit 434f8e4

Please sign in to comment.