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