blob: 837a6a73b3f9ced683004b87a47dfab251172a70 [file]
package com.intellij.configurationScript.schemaGenerators
import com.intellij.configurationScript.LOG
import com.intellij.configurationScript.SchemaGenerator
import com.intellij.configurationStore.ComponentSerializationUtil
import com.intellij.ide.plugins.IdeaPluginDescriptorImpl
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.openapi.components.BaseState
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.ServiceDescriptor
import com.intellij.openapi.diagnostic.runAndLogException
import com.intellij.openapi.extensions.PluginDescriptor
import com.intellij.util.ReflectionUtil
import gnu.trove.THashMap
import org.jetbrains.io.JsonObjectBuilder
internal class ComponentStateJsonSchemaGenerator : SchemaGenerator {
private val pathToStateClass: MutableMap<String, Class<out BaseState>> = THashMap()
private val objectSchemaGenerator = OptionClassJsonSchemaGenerator("classDefinitions")
override val definitionNodeKey: CharSequence?
get() = objectSchemaGenerator.definitionNodeKey
// schema is generated without project - we cannot rely on created component adapter for services
override fun generate(rootBuilder: JsonObjectBuilder) {
for (plugin in PluginManagerCore.getLoadedPlugins()) {
for (serviceDescriptor in (plugin as IdeaPluginDescriptorImpl).project.services) {
processServiceDescriptor(serviceDescriptor, plugin)
}
}
doGenerate(rootBuilder, pathToStateClass)
}
override fun generateDefinitions() = objectSchemaGenerator.describe()
internal fun doGenerate(rootBuilder: JsonObjectBuilder, pathToStateClass: Map<String, Class<out BaseState>>) {
if (pathToStateClass.isEmpty()) {
return
}
val pathToJsonObjectBuilder: MutableMap<String, JsonObjectBuilder> = THashMap()
for (path in pathToStateClass.keys.sorted()) {
val keys = path.split(".")
val jsonObjectBuilder: JsonObjectBuilder
LOG.assertTrue(keys.size < 3, "todo")
if (keys.size == 1) {
jsonObjectBuilder = rootBuilder
}
else {
jsonObjectBuilder = pathToJsonObjectBuilder.getOrPut(keys.subList(0, keys.size - 1).joinToString(".")) {
JsonObjectBuilder(StringBuilder(), indentLevel = 3)
}
}
jsonObjectBuilder.map(keys.last()) {
"type" to "object"
map("properties") {
buildJsonSchema(ReflectionUtil.newInstance(pathToStateClass.getValue(path)), this, objectSchemaGenerator)
}
"additionalProperties" to false
}
}
for ((key, value) in pathToJsonObjectBuilder) {
rootBuilder.map(key) {
"type" to "object"
rawBuilder("properties", value)
"additionalProperties" to false
}
}
}
private fun processServiceDescriptor(serviceDescriptor: ServiceDescriptor, plugin: PluginDescriptor) {
val schemaKeyPath = serviceDescriptor.configurationSchemaKey ?: return
LOG.runAndLogException {
val implClass = Class.forName(serviceDescriptor.implementation, /* initialize = */ false, plugin.pluginClassLoader)
if (!PersistentStateComponent::class.java.isAssignableFrom(implClass)) {
return
}
// must inherit from BaseState
@Suppress("UNCHECKED_CAST")
val stateClass = ComponentSerializationUtil.getStateClass<BaseState>(implClass as Class<out PersistentStateComponent<Any>>)
val oldValue = pathToStateClass.put(schemaKeyPath, stateClass)
if (oldValue != null) {
LOG.error("duplicated configurationSchemaKey $schemaKeyPath in plugin $plugin")
}
}
}
}