[workspace model] Fix ImlSerializationTest
[idea/community.git] / platform / workspaceModel / storage / src / com / intellij / workspaceModel / storage / impl / EntityStorageSerializerImpl.kt
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.workspaceModel.storage.impl
3
4 import com.esotericsoftware.kryo.Kryo
5 import com.esotericsoftware.kryo.Serializer
6 import com.esotericsoftware.kryo.io.Input
7 import com.esotericsoftware.kryo.io.Output
8 import com.esotericsoftware.kryo.serializers.DefaultSerializers
9 import com.esotericsoftware.kryo.serializers.FieldSerializer
10 import com.google.common.collect.HashBiMap
11 import com.google.common.collect.HashMultimap
12 import com.intellij.openapi.diagnostic.logger
13 import com.intellij.util.SmartList
14 import com.intellij.util.containers.*
15 import com.intellij.workspaceModel.storage.*
16 import com.intellij.workspaceModel.storage.impl.containers.ImmutableIntIntUniqueBiMap
17 import com.intellij.workspaceModel.storage.impl.containers.ImmutablePositiveIntIntBiMap
18 import com.intellij.workspaceModel.storage.impl.containers.ImmutablePositiveIntIntMultiMap
19 import com.intellij.workspaceModel.storage.impl.containers.LinkedBidirectionalMap
20 import com.intellij.workspaceModel.storage.impl.indices.EntityStorageInternalIndex
21 import com.intellij.workspaceModel.storage.impl.indices.MultimapStorageIndex
22 import com.intellij.workspaceModel.storage.impl.indices.VirtualFileIndex
23 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap
24 import org.jetbrains.annotations.TestOnly
25 import org.objenesis.instantiator.ObjectInstantiator
26 import org.objenesis.strategy.StdInstantiatorStrategy
27 import java.io.InputStream
28 import java.io.OutputStream
29 import java.util.*
30 import java.util.HashMap
31 import kotlin.collections.ArrayList
32 import kotlin.reflect.KVisibility
33 import kotlin.reflect.full.memberProperties
34 import kotlin.reflect.jvm.jvmName
35
36 class EntityStorageSerializerImpl(private val typesResolver: EntityTypesResolver,
37                                   private val virtualFileManager: VirtualFileUrlManager) : EntityStorageSerializer {
38   private val KRYO_BUFFER_SIZE = 64 * 1024
39
40   @set:TestOnly
41   override var serializerDataFormatVersion: String = "v1"
42
43   private fun createKryo(): Kryo {
44     val kryo = Kryo()
45
46     kryo.isRegistrationRequired = StrictMode.enabled
47     kryo.instantiatorStrategy = StdInstantiatorStrategy()
48
49     kryo.register(VirtualFileUrl::class.java, object : Serializer<VirtualFileUrl>(false, true) {
50       override fun write(kryo: Kryo, output: Output, obj: VirtualFileUrl) {
51         // TODO Write IDs only
52         output.writeString(obj.url)
53       }
54
55       override fun read(kryo: Kryo, input: Input, type: Class<VirtualFileUrl>): VirtualFileUrl =
56         virtualFileManager.fromUrl(input.readString())
57     })
58
59     kryo.register(EntityId::class.java, object : Serializer<EntityId>(false, true) {
60       override fun write(kryo: Kryo, output: Output, `object`: EntityId) {
61         output.writeInt(`object`.arrayId)
62         val typeClass = `object`.clazz.findEntityClass<WorkspaceEntity>()
63         val typeInfo = TypeInfo(typeClass.name, typesResolver.getPluginId(typeClass))
64         kryo.writeClassAndObject(output, typeInfo)
65       }
66
67       override fun read(kryo: Kryo, input: Input, type: Class<EntityId>): EntityId {
68         val arrayId = input.readInt()
69         val clazzInfo = kryo.readClassAndObject(input) as TypeInfo
70         val clazz = typesResolver.resolveClass(clazzInfo.name, clazzInfo.pluginId)
71         return EntityId(arrayId, clazz.toClassId())
72       }
73     })
74
75     kryo.register(HashMultimap::class.java, object : Serializer<HashMultimap<*, *>>(false, true) {
76       override fun write(kryo: Kryo, output: Output, `object`: HashMultimap<*, *>) {
77         val res = HashMap<Any, Collection<Any>>()
78         `object`.asMap().forEach { (key, values) ->
79           res[key] = ArrayList(values)
80         }
81         kryo.writeClassAndObject(output, res)
82       }
83
84       override fun read(kryo: Kryo, input: Input, type: Class<HashMultimap<*, *>>): HashMultimap<*, *> {
85         val res = HashMultimap.create<Any, Any>()
86         val map = kryo.readClassAndObject(input) as HashMap<*, Collection<*>>
87         map.forEach { (key, values) ->
88           res.putAll(key, values)
89         }
90         return res
91       }
92     })
93
94     kryo.register(ConnectionId::class.java, object : Serializer<ConnectionId>(false, true) {
95       override fun write(kryo: Kryo, output: Output, `object`: ConnectionId) {
96         val parentClassType = `object`.parentClass.findEntityClass<WorkspaceEntity>()
97         val childClassType = `object`.childClass.findEntityClass<WorkspaceEntity>()
98         val parentTypeInfo = TypeInfo(parentClassType.name, typesResolver.getPluginId(parentClassType))
99         val childTypeInfo = TypeInfo(childClassType.name, typesResolver.getPluginId(childClassType))
100
101         kryo.writeClassAndObject(output, parentTypeInfo)
102         kryo.writeClassAndObject(output, childTypeInfo)
103         output.writeString(`object`.connectionType.name)
104         output.writeBoolean(`object`.isParentNullable)
105         output.writeBoolean(`object`.isChildNullable)
106       }
107
108       override fun read(kryo: Kryo, input: Input, type: Class<ConnectionId>): ConnectionId {
109         val parentClazzInfo = kryo.readClassAndObject(input) as TypeInfo
110         val childClazzInfo = kryo.readClassAndObject(input) as TypeInfo
111
112         val parentClass = typesResolver.resolveClass(parentClazzInfo.name, parentClazzInfo.pluginId) as Class<WorkspaceEntity>
113         val childClass = typesResolver.resolveClass(childClazzInfo.name, childClazzInfo.pluginId) as Class<WorkspaceEntity>
114
115         val connectionType = ConnectionId.ConnectionType.valueOf(input.readString())
116         val parentNullable = input.readBoolean()
117         val childNullable = input.readBoolean()
118         return ConnectionId.create(parentClass, childClass, connectionType, parentNullable, childNullable)
119       }
120     })
121
122     kryo.register(ImmutableEntitiesBarrel::class.java, object : Serializer<ImmutableEntitiesBarrel>(false, true) {
123       override fun write(kryo: Kryo, output: Output, `object`: ImmutableEntitiesBarrel) {
124         val res = HashMap<TypeInfo, EntityFamily<*>>()
125         `object`.entityFamilies.forEachIndexed { i, v ->
126           if (v == null) return@forEachIndexed
127           val clazz = i.findEntityClass<WorkspaceEntity>()
128           val typeInfo = TypeInfo(clazz.name, typesResolver.getPluginId(clazz))
129           res[typeInfo] = v
130         }
131         kryo.writeClassAndObject(output, res)
132       }
133
134       override fun read(kryo: Kryo, input: Input, type: Class<ImmutableEntitiesBarrel>): ImmutableEntitiesBarrel {
135         val mutableBarrel = MutableEntitiesBarrel.create()
136         val families = kryo.readClassAndObject(input) as HashMap<TypeInfo, EntityFamily<*>>
137         for ((typeInfo, family) in families) {
138           val classId = typesResolver.resolveClass(typeInfo.name, typeInfo.pluginId).toClassId()
139           mutableBarrel.fillEmptyFamilies(classId)
140           mutableBarrel.entityFamilies[classId] = family
141         }
142         return mutableBarrel.toImmutable()
143       }
144     })
145
146     kryo.register(TypeInfo::class.java)
147
148     // TODO Dedup with OCSerializers
149     // TODO Reuse OCSerializer.registerUtilitySerializers ?
150     // TODO Scan OCSerializer for useful kryo settings and tricks
151     kryo.register(java.util.ArrayList::class.java).instantiator = ObjectInstantiator { ArrayList<Any>() }
152     kryo.register(HashMap::class.java).instantiator = ObjectInstantiator { HashMap<Any, Any>() }
153     kryo.register(SmartList::class.java).instantiator = ObjectInstantiator { SmartList<Any>() }
154     kryo.register(LinkedHashMap::class.java).instantiator = ObjectInstantiator { LinkedHashMap<Any, Any>() }
155     kryo.register(BidirectionalMap::class.java).instantiator = ObjectInstantiator { BidirectionalMap<Any, Any>() }
156     kryo.register(HashSet::class.java).instantiator = ObjectInstantiator { HashSet<Any>() }
157     kryo.register(BidirectionalMultiMap::class.java).instantiator = ObjectInstantiator { BidirectionalMultiMap<Any, Any>() }
158     kryo.register(HashBiMap::class.java).instantiator = ObjectInstantiator { HashBiMap.create<Any, Any>() }
159     kryo.register(LinkedBidirectionalMap::class.java).instantiator = ObjectInstantiator { LinkedBidirectionalMap<Any, Any>() }
160     kryo.register(Int2IntOpenHashMap::class.java).instantiator = ObjectInstantiator { Int2IntOpenHashMap() }
161
162     @Suppress("ReplaceJavaStaticMethodWithKotlinAnalog")
163     kryo.register(Arrays.asList("a").javaClass).instantiator = ObjectInstantiator { java.util.ArrayList<Any>() }
164
165     kryo.register(ByteArray::class.java)
166     kryo.register(ImmutableEntityFamily::class.java)
167     kryo.register(RefsTable::class.java)
168     kryo.register(ImmutablePositiveIntIntBiMap::class.java)
169     kryo.register(ImmutableIntIntUniqueBiMap::class.java)
170     kryo.register(VirtualFileIndex::class.java)
171     kryo.register(EntityStorageInternalIndex::class.java)
172     kryo.register(ImmutablePositiveIntIntMultiMap.ByList::class.java)
173     kryo.register(IntArray::class.java)
174     kryo.register(Pair::class.java)
175     kryo.register(MultimapStorageIndex::class.java)
176     kryo.register(VirtualFileIndex.VirtualFileUrlInfo::class.java)
177
178     registerFieldSerializer(kryo, Collections.unmodifiableCollection<Any>(emptySet()).javaClass) {
179       Collections.unmodifiableCollection(emptySet())
180     }
181     registerFieldSerializer(kryo, Collections.unmodifiableSet<Any>(emptySet()).javaClass) { Collections.unmodifiableSet(emptySet()) }
182     registerFieldSerializer(kryo, Collections.unmodifiableList<Any>(emptyList()).javaClass) { Collections.unmodifiableList(emptyList()) }
183     registerFieldSerializer(kryo, Collections.unmodifiableMap<Any, Any>(emptyMap()).javaClass) { Collections.unmodifiableMap(emptyMap()) }
184
185     kryo.register(Collections.EMPTY_LIST.javaClass, DefaultSerializers.CollectionsEmptyListSerializer())
186     kryo.register(Collections.EMPTY_MAP.javaClass, DefaultSerializers.CollectionsEmptyMapSerializer())
187     kryo.register(Collections.EMPTY_SET.javaClass, DefaultSerializers.CollectionsEmptySetSerializer())
188     kryo.register(listOf(null).javaClass, DefaultSerializers.CollectionsSingletonListSerializer())
189     kryo.register(Collections.singletonMap<Any, Any>(null, null).javaClass, DefaultSerializers.CollectionsSingletonMapSerializer())
190     kryo.register(setOf(null).javaClass, DefaultSerializers.CollectionsSingletonSetSerializer())
191
192     registerSingletonSerializer(kryo) { ContainerUtil.emptyList<Any>() }
193     registerSingletonSerializer(kryo) { MostlySingularMultiMap.emptyMap<Any, Any>() }
194     registerSingletonSerializer(kryo) { MultiMap.empty<Any, Any>() }
195
196     registerSingletonSerializer(kryo) { emptyMap<Any, Any>() }
197     registerSingletonSerializer(kryo) { emptyList<Any>() }
198     registerSingletonSerializer(kryo) { emptySet<Any>() }
199
200     return kryo
201   }
202
203   // TODO Dedup with OCSerializer
204   private inline fun <reified T : Any> registerFieldSerializer(kryo: Kryo, type: Class<T> = T::class.java, crossinline create: () -> T) =
205     registerSerializer(kryo, type, FieldSerializer(kryo, type), ObjectInstantiator { create() })
206
207   @JvmSynthetic
208   private inline fun registerSingletonSerializer(kryo: Kryo, crossinline getter: () -> Any) {
209     val getter1 = ObjectInstantiator { getter() }
210     registerSerializer(kryo, getter1.newInstance().javaClass, EmptySerializer, getter1)
211   }
212
213   object EmptySerializer : Serializer<Any>(false, true) {
214     override fun write(kryo: Kryo?, output: Output?, `object`: Any?) {}
215     override fun read(kryo: Kryo, input: Input?, type: Class<Any>): Any? = kryo.newInstance(type)
216   }
217
218   private fun <T : Any> registerSerializer(kryo: Kryo, type: Class<T>, serializer: Serializer<in T>, initializer: ObjectInstantiator<T>) {
219     kryo.register(type, serializer).apply { instantiator = initializer }
220   }
221
222   /**
223    * Collect all classes existing in entity data.
224    * [simpleClasses] - set of classes
225    * [objectClasses] - set of kotlin objects
226    */
227   private fun recursiveClassFinder(kryo: Kryo, entity: Any, simpleClasses: MutableSet<TypeInfo>, objectClasses: MutableSet<TypeInfo>) {
228     val kClass = entity::class
229     val typeInfo = TypeInfo(kClass.jvmName, typesResolver.getPluginId(kClass.java))
230     if (kryo.classResolver.getRegistration(kClass.java) != null) return
231
232     val objectInstance = kClass.objectInstance
233     if (objectInstance != null) {
234       objectClasses += typeInfo
235     }
236     else {
237       simpleClasses += typeInfo
238     }
239
240     kClass.memberProperties.forEach {
241       val retType = (it.returnType as Any).toString()
242
243       if ((retType.startsWith("kotlin") || retType.startsWith("java"))
244           && !retType.startsWith("kotlin.collections.List")
245           && !retType.startsWith("java.util.List")
246       ) return@forEach
247
248       if (it.visibility != KVisibility.PUBLIC) return@forEach
249       val property = it.getter.call(entity) ?: return@forEach
250       recursiveClassFinder(kryo, property, simpleClasses, objectClasses)
251
252       if (property is List<*>) {
253         property.filterNotNull().forEach { listItem ->
254           recursiveClassFinder(kryo, listItem, simpleClasses, objectClasses)
255         }
256       }
257     }
258   }
259
260   override fun serializeCache(stream: OutputStream, storage: WorkspaceEntityStorage) {
261     storage as WorkspaceEntityStorageImpl
262     storage.assertConsistencyInStrictMode()
263
264     val output = Output(stream, KRYO_BUFFER_SIZE)
265     try {
266       val kryo = createKryo()
267
268       // Save version
269       output.writeString(serializerDataFormatVersion)
270
271       // Collect all classes existing in entity data
272       val simpleClasses = HashSet<TypeInfo>()
273       val objectClasses = HashSet<TypeInfo>()
274       storage.entitiesByType.entityFamilies.filterNotNull().forEach { family ->
275         family.entities.filterNotNull().forEach { recursiveClassFinder(kryo, it, simpleClasses, objectClasses) }
276       }
277
278       // Serialize and register types of kotlin objects
279       output.writeVarInt(objectClasses.size, true)
280       objectClasses.forEach {
281         kryo.register(typesResolver.resolveClass(it.name, it.pluginId))
282         kryo.writeClassAndObject(output, it)
283       }
284
285       // Serialize and register all types existing in entity data
286       output.writeVarInt(simpleClasses.size, true)
287       simpleClasses.forEach {
288         kryo.register(typesResolver.resolveClass(it.name, it.pluginId))
289         kryo.writeClassAndObject(output, it)
290       }
291
292       // Serialize and register persistent ids
293       val persistentIds = storage.indexes.persistentIdIndex.entries().toSet()
294       output.writeVarInt(persistentIds.size, true)
295       persistentIds.forEach {
296         val typeInfo = TypeInfo(it::class.jvmName, typesResolver.getPluginId(it::class.java))
297         kryo.register(it::class.java)
298         kryo.writeClassAndObject(output, typeInfo)
299       }
300
301       // Write entity data and references
302       kryo.writeClassAndObject(output, storage.entitiesByType)
303       kryo.writeClassAndObject(output, storage.refs)
304
305       // Write indexes
306       kryo.writeClassAndObject(output, storage.indexes.softLinks)
307       kryo.writeClassAndObject(output, storage.indexes.virtualFileIndex)
308       kryo.writeClassAndObject(output, storage.indexes.entitySourceIndex)
309       kryo.writeClassAndObject(output, storage.indexes.persistentIdIndex)
310     }
311     finally {
312       output.flush()
313     }
314   }
315
316   override fun deserializeCache(stream: InputStream): WorkspaceEntityStorageBuilder? {
317     Input(stream, KRYO_BUFFER_SIZE).use { input ->
318       val kryo = createKryo()
319
320       // Read version
321       val cacheVersion = input.readString()
322       if (cacheVersion != serializerDataFormatVersion) {
323         logger.info("Cache isn't loaded. Current version of cache: $serializerDataFormatVersion, version of cache file: $cacheVersion")
324         return null
325       }
326
327       // Read and register all kotlin objects
328       val objectCount = input.readVarInt(true)
329       repeat(objectCount) {
330         val objectClass = kryo.readClassAndObject(input) as TypeInfo
331         registerSingletonSerializer(kryo) { typesResolver.resolveClass(objectClass.name, objectClass.pluginId).kotlin.objectInstance!! }
332       }
333
334       // Read and register all types in entity data
335       val nonObjectCount = input.readVarInt(true)
336       repeat(nonObjectCount) {
337         val objectClass = kryo.readClassAndObject(input) as TypeInfo
338         kryo.register(typesResolver.resolveClass(objectClass.name, objectClass.pluginId))
339       }
340
341       // Read and register persistent ids
342       val persistentIdCount = input.readVarInt(true)
343       repeat(persistentIdCount) {
344         val objectClass = kryo.readClassAndObject(input) as TypeInfo
345         kryo.register(typesResolver.resolveClass(objectClass.name, objectClass.pluginId))
346       }
347
348       // Read entity data and references
349       val entitiesBarrel = kryo.readClassAndObject(input) as ImmutableEntitiesBarrel
350       val refsTable = kryo.readClassAndObject(input) as RefsTable
351
352       // Read indexes
353       val softLinks = kryo.readClassAndObject(input) as MultimapStorageIndex
354       val virtualFileIndex = kryo.readClassAndObject(input) as VirtualFileIndex
355       val entitySourceIndex = kryo.readClassAndObject(input) as EntityStorageInternalIndex<EntitySource>
356       val persistentIdIndex = kryo.readClassAndObject(input) as EntityStorageInternalIndex<PersistentEntityId<*>>
357       val storageIndexes = StorageIndexes(softLinks, virtualFileIndex, entitySourceIndex, persistentIdIndex)
358
359
360       val storage = WorkspaceEntityStorageImpl(entitiesBarrel, refsTable, storageIndexes)
361       storage.assertConsistencyInStrictMode()
362       val builder = WorkspaceEntityStorageBuilderImpl.from(storage)
363
364       builder.entitiesByType.entityFamilies.forEach { family ->
365         family?.entities?.asSequence()?.filterNotNull()?.forEach { entityData -> builder.createAddEvent(entityData) }
366       }
367
368       return builder
369     }
370   }
371
372   private data class TypeInfo(val name: String, val pluginId: String?)
373
374   companion object {
375     val logger = logger<EntityStorageSerializerImpl>()
376   }
377 }