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
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
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
35 class EntityStorageSerializerImpl(private val typesResolver: EntityTypesResolver,
36 private val virtualFileManager: VirtualFileUrlManager) : EntityStorageSerializer {
37 private val KRYO_BUFFER_SIZE = 64 * 1024
40 override var serializerDataFormatVersion: String = "v1"
42 private fun createKryo(): Kryo {
45 kryo.isRegistrationRequired = StrictMode.enabled
46 kryo.instantiatorStrategy = StdInstantiatorStrategy()
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)
54 override fun read(kryo: Kryo, input: Input, type: Class<VirtualFileUrl>): VirtualFileUrl =
55 virtualFileManager.fromUrl(input.readString())
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)
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())
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)
80 kryo.writeClassAndObject(output, res)
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)
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))
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)
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
111 val parentClass = typesResolver.resolveClass(parentClazzInfo.name, parentClazzInfo.pluginId) as Class<WorkspaceEntity>
112 val childClass = typesResolver.resolveClass(childClazzInfo.name, childClazzInfo.pluginId) as Class<WorkspaceEntity>
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)
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))
130 kryo.writeClassAndObject(output, res)
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
141 return mutableBarrel.toImmutable()
145 kryo.register(TypeInfo::class.java)
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() }
160 @Suppress("ReplaceJavaStaticMethodWithKotlinAnalog")
161 kryo.register(Arrays.asList("a").javaClass).instantiator = ObjectInstantiator { java.util.ArrayList<Any>() }
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)
176 registerFieldSerializer(kryo, Collections.unmodifiableCollection<Any>(emptySet()).javaClass) {
177 Collections.unmodifiableCollection(emptySet())
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()) }
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())
190 registerSingletonSerializer(kryo) { ContainerUtil.emptyList<Any>() }
191 registerSingletonSerializer(kryo) { MostlySingularMultiMap.emptyMap<Any, Any>() }
192 registerSingletonSerializer(kryo) { MultiMap.empty<Any, Any>() }
194 registerSingletonSerializer(kryo) { emptyMap<Any, Any>() }
195 registerSingletonSerializer(kryo) { emptyList<Any>() }
196 registerSingletonSerializer(kryo) { emptySet<Any>() }
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() })
206 private inline fun registerSingletonSerializer(kryo: Kryo, crossinline getter: () -> Any) {
207 val getter1 = ObjectInstantiator { getter() }
208 registerSerializer(kryo, getter1.newInstance().javaClass, EmptySerializer, getter1)
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)
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 }
221 * Collect all classes existing in entity data.
222 * [simpleClasses] - set of classes
223 * [objectClasses] - set of kotlin objects
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
230 val objectInstance = kClass.objectInstance
231 if (objectInstance != null) {
232 objectClasses += typeInfo
235 simpleClasses += typeInfo
238 kClass.memberProperties.forEach {
239 val retType = (it.returnType as Any).toString()
241 if ((retType.startsWith("kotlin") || retType.startsWith("java"))
242 && !retType.startsWith("kotlin.collections.List")
243 && !retType.startsWith("java.util.List")
246 if (it.visibility != KVisibility.PUBLIC) return@forEach
247 val property = it.getter.call(entity) ?: return@forEach
248 recursiveClassFinder(kryo, property, simpleClasses, objectClasses)
250 if (property is List<*>) {
251 property.filterNotNull().forEach { listItem ->
252 recursiveClassFinder(kryo, listItem, simpleClasses, objectClasses)
258 override fun serializeCache(stream: OutputStream, storage: WorkspaceEntityStorage) {
259 storage as WorkspaceEntityStorageImpl
260 storage.assertConsistencyInStrictMode()
262 val output = Output(stream, KRYO_BUFFER_SIZE)
264 val kryo = createKryo()
267 output.writeString(serializerDataFormatVersion)
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) }
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)
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)
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)
299 // Write entity data and references
300 kryo.writeClassAndObject(output, storage.entitiesByType)
301 kryo.writeClassAndObject(output, storage.refs)
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)
314 override fun deserializeCache(stream: InputStream): WorkspaceEntityStorageBuilder? {
315 Input(stream, KRYO_BUFFER_SIZE).use { input ->
316 val kryo = createKryo()
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")
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!! }
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))
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))
346 // Read entity data and references
347 val entitiesBarrel = kryo.readClassAndObject(input) as ImmutableEntitiesBarrel
348 val refsTable = kryo.readClassAndObject(input) as RefsTable
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)
358 val storage = WorkspaceEntityStorageImpl(entitiesBarrel, refsTable, storageIndexes)
359 storage.assertConsistencyInStrictMode()
360 val builder = WorkspaceEntityStorageBuilderImpl.from(storage)
362 builder.entitiesByType.entityFamilies.forEach { family ->
363 family?.entities?.asSequence()?.filterNotNull()?.forEach { entityData -> builder.createAddEvent(entityData) }
370 private data class TypeInfo(val name: String, val pluginId: String?)
373 val logger = logger<EntityStorageSerializerImpl>()