Skip to content

Commit

Permalink
support using of kotlinx serialization for persistence state component
Browse files Browse the repository at this point in the history
GitOrigin-RevId: bf9ff3e6be6c8ad4712c68eda903371af0e647d7
  • Loading branch information
develar authored and intellij-monorepo-bot committed Jan 8, 2022
1 parent 89e6984 commit 90c5cbb
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 220 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,11 @@ internal class XmlSerializerMapTest {
@Test
fun `no nullize of empty data`() {
val element = JDOMUtil.load("""
<extensions>
<option name="extensionsMap" />
</extensions>
<state>
<![CDATA[{
}
}]]>
</state>
""".trimIndent())
val result = element.deserialize(PluginFeatureMap::class.java)
assertThat(result.featureMap).isNotNull()
Expand All @@ -292,43 +294,40 @@ internal class XmlSerializerMapTest {
@Test
fun `knownExtensions serialization`() {
val pluginData = PluginData("foo", "Foo")
val extensions = PluginFeatureMap(mapOf("foo" to setOf(pluginData)))
val extensions = PluginFeatureMap(mapOf("foo" to hashSetOf(pluginData)))

testSerializer(
"""
<features>
<featureMap>
<entry key="foo">
<plugins>
<dataSet>
<plugin pluginId="foo" pluginName="Foo" bundled="false" fromCustomRepository="false" />
</dataSet>
</plugins>
</entry>
</featureMap>
<option name="lastUpdateTime" value="0" />
</features>
<state><![CDATA[{
"featureMap": {
"foo": {
"dataSet": [
{
"pluginIdString": "foo",
"nullablePluginName": "Foo"
}
]
}
}
}]]></state>
""".trimIndent(),
extensions,
)
}

@Test
fun `PluginFeatureCacheService serialization`() {
val state = PluginFeatureCacheService.MyState()
state.extensions = PluginFeatureMap(mapOf())
val component = PluginFeatureCacheService()
component.extensions = PluginFeatureMap()

testSerializer(
"""
<State>
<option name="extensions">
<features lastUpdateTime="0">
<featureMap />
</features>
</option>
</State>
<state><![CDATA[{
"extensions": {
}
}]]></state>
""".trimIndent(),
state,
component.state,
)
}

Expand All @@ -341,9 +340,13 @@ internal class XmlSerializerMapTest {

testSerializer(
"""
<featurePlugin displayName="foo">
<plugin pluginId="foo" pluginName="Foo" bundled="false" fromCustomRepository="false" />
</featurePlugin>
<state><![CDATA[{
"displayName": "foo",
"pluginData": {
"pluginIdString": "foo",
"nullablePluginName": "Foo"
}
}]]></state>
""".trimIndent(),
pluginData,
)
Expand All @@ -352,17 +355,16 @@ internal class XmlSerializerMapTest {
@Test
fun `pluginFeatureService serialization`() {
val state = PluginFeatureService.State()
state["foo"] = PluginFeatureService.FeaturePluginsList()
state.features.put("foo", PluginFeatureService.FeaturePluginList())

testSerializer(
"""
<pluginFeatures>
<features>
<entry key="foo">
<features />
</entry>
</features>
</pluginFeatures>
<state><![CDATA[{
"features": {
"foo": {
}
}
}]]></state>
""".trimIndent(),
state,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,59 +1,40 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("ReplaceGetOrSet")

package com.intellij.ide.plugins

import com.intellij.ide.plugins.advertiser.FeaturePluginData
import com.intellij.ide.plugins.advertiser.PluginData
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.*
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.util.xmlb.annotations.Tag
import com.intellij.util.xmlb.annotations.XMap
import com.intellij.openapi.util.SimpleModificationTracker
import kotlinx.serialization.Serializable

@Service(Service.Level.APP)
@State(
name = "PluginFeatureService",
storages = [Storage(StoragePathMacros.CACHE_FILE, roamingType = RoamingType.DISABLED)],
)
class PluginFeatureService : SimplePersistentStateComponent<PluginFeatureService.State>(State()) {
@Tag("features")
class FeaturePluginsList : BaseState() {

@get:XMap
val featureMap by linkedMap<String, FeaturePluginData>()

operator fun set(implementationName: String, pluginData: FeaturePluginData) {
if (featureMap.put(implementationName, pluginData) != pluginData) {
incrementModificationCount()
}
}

operator fun get(implementationName: String): FeaturePluginData? = featureMap[implementationName]
@State(name = "PluginFeatureService", storages = [Storage(StoragePathMacros.CACHE_FILE)])
class PluginFeatureService : SerializablePersistentStateComponent<PluginFeatureService.State>(State()) {
companion object {
@JvmStatic
val instance: PluginFeatureService
get() = ApplicationManager.getApplication().getService(PluginFeatureService::class.java)
}

@Tag("pluginFeatures")
class State : BaseState() {
private val tracker = SimpleModificationTracker()

@get:XMap
val features by linkedMap<String, FeaturePluginsList>()
@Serializable
data class FeaturePluginList(val featureMap: MutableMap<String, FeaturePluginData> = HashMap())

operator fun set(featureType: String, pluginsList: FeaturePluginsList) {
if (features.put(featureType, pluginsList) != pluginsList) {
incrementModificationCount()
}
}
@Serializable
data class State(val features: MutableMap<String, FeaturePluginList> = HashMap())

operator fun get(featureType: String): FeaturePluginsList {
return features.getOrPut(featureType) {
incrementModificationCount()
FeaturePluginsList()
}
}
}
override fun getStateModificationCount() = tracker.modificationCount

companion object {
@JvmStatic
val instance: PluginFeatureService
get() = ApplicationManager.getApplication().getService(PluginFeatureService::class.java)
private fun getOrCreateFeature(featureType: String): FeaturePluginList {
return state.features.computeIfAbsent(featureType) {
tracker.incModificationCount()
FeaturePluginList()
}
}

fun <T> collectFeatureMapping(
Expand All @@ -62,18 +43,24 @@ class PluginFeatureService : SimplePersistentStateComponent<PluginFeatureService
idMapping: (T) -> String,
displayNameMapping: (T) -> String,
) {
val pluginsList = state[featureType]
val pluginList = getOrCreateFeature(featureType)
var changed = false
ep.processWithPluginDescriptor { ext, descriptor ->
pluginsList[idMapping(ext)] = FeaturePluginData(
val pluginData = FeaturePluginData(
displayNameMapping(ext),
PluginData(descriptor),
)
if (pluginList.featureMap.put(idMapping(ext), pluginData) != pluginData) {
changed = true
}
}

if (changed) {
tracker.incModificationCount()
}
}

fun getPluginForFeature(featureType: String, implementationName: String): FeaturePluginData? {
return state[featureType].let { pluginsList ->
pluginsList[implementationName]
}
return state.features.get(featureType)?.featureMap?.get(implementationName)
}
}
81 changes: 29 additions & 52 deletions platform/ide-core/src/com/intellij/ide/plugins/advertiser/data.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,85 +7,62 @@ import com.intellij.openapi.extensions.PluginDescriptor
import com.intellij.openapi.extensions.PluginId
import com.intellij.openapi.util.Comparing
import com.intellij.openapi.util.ModificationTracker
import com.intellij.util.xmlb.annotations.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import java.util.concurrent.TimeUnit

@Tag("plugin")
class PluginData @JvmOverloads constructor(
@Attribute("pluginId") val pluginIdString: String = "",
@Attribute("pluginName") private val nullablePluginName: String? = null,
@Attribute("bundled") val isBundled: Boolean = false,
@Attribute("fromCustomRepository") val isFromCustomRepository: Boolean = false,
@Serializable
data class PluginData(
val pluginIdString: String = "",
private val nullablePluginName: String? = null,
val isBundled: Boolean = false,
val isFromCustomRepository: Boolean = false,
) : Comparable<PluginData> {
val pluginId: PluginId
get() = PluginId.getId(pluginIdString)

val pluginId: PluginId get() = PluginId.getId(pluginIdString)

val pluginName: String get() = nullablePluginName ?: pluginIdString
val pluginName: String
get() = nullablePluginName ?: pluginIdString

constructor(descriptor: PluginDescriptor) : this(
descriptor.pluginId.idString,
descriptor.name,
descriptor.isBundled,
)

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as PluginData
return isBundled == other.isBundled
&& pluginIdString == other.pluginIdString
&& (nullablePluginName == null || nullablePluginName == other.nullablePluginName)
}

override fun hashCode(): Int {
var result = pluginIdString.hashCode()
result = 31 * result + isBundled.hashCode()
result = 31 * result + (nullablePluginName?.hashCode() ?: 0)
return result
}

override fun compareTo(other: PluginData): Int {
return if (isBundled && !other.isBundled) -1
else if (!isBundled && other.isBundled) 1
else Comparing.compare(pluginIdString, other.pluginIdString)
}
}

@Tag("featurePlugin")
class FeaturePluginData @JvmOverloads constructor(
@Attribute("displayName") val displayName: String = "",
@Property(surroundWithTag = false) val pluginData: PluginData = PluginData(),
@Serializable
data class FeaturePluginData(
val displayName: String = "",
val pluginData: PluginData = PluginData()
)

@Tag("plugins")
class PluginDataSet @JvmOverloads constructor(dataSet: Set<PluginData> = emptySet()) {
@JvmField
@XCollection(style = XCollection.Style.v2)
val dataSet = HashSet<PluginData>()

init {
this.dataSet.addAll(dataSet)
private fun convertMap(map: Map<String, MutableSet<PluginData>>): MutableMap<String, PluginDataSet> {
val result = HashMap<String, PluginDataSet>()
for (entry in map.entries) {
result.put(entry.key, PluginDataSet(entry.value))
}
return result
}

@Tag("features")
class PluginFeatureMap @JvmOverloads constructor(initialFeatureMap: Map<String, Set<PluginData>> = emptyMap()) : ModificationTracker {
@JvmField
@XMap
val featureMap = HashMap<String, PluginDataSet>()
@Serializable
data class PluginDataSet(val dataSet: MutableSet<PluginData> = HashSet())

@JvmField
@Attribute
@Serializable
data class PluginFeatureMap(
val featureMap: MutableMap<String, PluginDataSet> = HashMap(),
var lastUpdateTime: Long = 0L

) : ModificationTracker {
@Transient
private var modificationCount = 0L

init {
for (entry in initialFeatureMap.entries) {
featureMap.put(entry.key, PluginDataSet(entry.value))
}
}
constructor(map: Map<String, MutableSet<PluginData>>) : this(convertMap(map))

fun update(newFeatureMap: Map<String, Set<PluginData>>) {
for ((id, plugins) in newFeatureMap) {
Expand Down
47 changes: 47 additions & 0 deletions platform/object-serializer/src/xml/KotlinxSerializationBinding.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.serialization.xml

import com.intellij.util.XmlElement
import com.intellij.util.xmlb.NotNullDeserializeBinding
import com.intellij.util.xmlb.SerializationFilter
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json
import org.jdom.CDATA
import org.jdom.Element

@OptIn(ExperimentalSerializationApi::class)
private val json = Json {
prettyPrint = true
prettyPrintIndent = " "
ignoreUnknownKeys = true
}

internal class KotlinxSerializationBinding(private val serializer: KSerializer<Any>) : NotNullDeserializeBinding() {
override fun serialize(o: Any, context: Any?, filter: SerializationFilter?): Any {
val element = Element("state")
element.addContent(CDATA(json.encodeToString(serializer, o)))
return element
}

override fun isBoundTo(element: Element): Boolean {
throw UnsupportedOperationException("Only root object is supported")
}

override fun isBoundTo(element: XmlElement): Boolean {
throw UnsupportedOperationException("Only root object is supported")
}

override fun deserialize(context: Any?, element: Element): Any {
val cdata = element.content.firstOrNull() as? CDATA
if (cdata == null) {
LOG.debug("incorrect data (old format?) for $serializer")
return json.decodeFromString(serializer, "{}")
}
return json.decodeFromString(serializer, cdata.text)
}

override fun deserialize(context: Any?, element: XmlElement): Any {
throw UnsupportedOperationException("Only JDOM is supported for now")
}
}

0 comments on commit 90c5cbb

Please sign in to comment.