Settings - heterogeneous list fails to load properly - loads nulls instead

For my plugin configuration I need user to select one of two backing classes for each item in list of Tool Window configurations.

  • UI is working just fine with proper bindings for both classes within list
  • Upon any save I always get properly looking entries within XML document backing my settings
  • The problem happens when it is time to load settings - the loadState(state: Data) function loads my list as list full of nulls, which doesn’t happen when I have a list of single concrete classes.

I also tried to made my list as list of Any - a desperate measure that doesn’t work either

Is there anything that can be done here, or is this somewhere deep in IntelliJ core that makes these heterogeneous list fail to load properly?

Bellow is example I cut down from my current version which I still explore, so sorry for some unpleasantly looking parts you may find.

This is core of problem:

sealed class Tool_Window_Config {

    data class Windowed_Tool_Window_Config (
        var x: Int =0,
        var y: Int = 0,
        var width: Int=300,
        var height: Int=300,
        var always_on_top: Boolean = true
    ): Tool_Window_Config()

    data class Docked_Tool_Window_Config(
        var size: Int = 2000,
        var dock_type: Tool_Window_Dock_Type = Tool_Window_Dock_Type.Right
    ): Tool_Window_Config()
}

data class Data(
    var variants: MutableList<Any> = mutableListOf(
        Tool_Window_Config.Windowed_Tool_Window_Config(),
        Tool_Window_Config.Windowed_Tool_Window_Config(),
        Tool_Window_Config.Docked_Tool_Window_Config(),
        Tool_Window_Config.Docked_Tool_Window_Config(),
        Tool_Window_Config.Docked_Tool_Window_Config(),
    )
)

@Service
@State(
    name = "com.github.jkavalec.Perfect_Fit",
    storages = [Storage("Perfect_Fit.xml")]
)
class Plugin_State : PersistentStateComponent<Data> {
    var data = Data()

    override fun getState(): Data? {
        return data
    }

// !
// ! Issue is here
// !
    override fun loadState(state: Data) {
        data = state
    }

    companion object {
        val instance: Plugin_State
            get() = ApplicationManager.getApplication().service<Plugin_State>()
    }

}

XML config after UI state is saved looks all right:

<application>
  <component name="com.github.jkavalec.Perfect_Fit">
    <option name="variants">
      <list>
        <Windowed_Tool_Window_Config />
        <Docked_Tool_Window_Config>
          <option name="size" value="299" />
        </Docked_Tool_Window_Config>
        <Windowed_Tool_Window_Config>
          <option name="y" value="50" />
        </Windowed_Tool_Window_Config>
        <Windowed_Tool_Window_Config />
      </list>
    </option>
  </component>
</application>

UI for added context:

class Plugin_Configurable : BoundConfigurable("") {
    lateinit var component: DialogPanel
    lateinit var list_placeholder: Placeholder

    override fun isModified(): Boolean {
        return component.isModified()
    }

    override fun apply() {
        super.apply()
    }

    fun create_list(): JComponent {
        return panel {
            Plugin_State.instance.data.variants.forEachIndexed { index, item ->
                println(item)
                row {
                    when(item) {
                        is Tool_Window_Config.Docked_Tool_Window_Config -> {
                            val s = segmentedButton(listOf("Docked", "Windowed")) {
                                text = it
                                icon = if(it == "Docked")AllIcons.Plugins.Downloads else AllIcons.General.OpenInToolWindow
                            }.apply {
                                selectedItem = "Docked"
                            }
                            s.whenItemSelected {
                                SwingUtilities.invokeLater {
                                    Plugin_State.instance.data.variants[index] = Tool_Window_Config.Windowed_Tool_Window_Config()
                                    list_placeholder.component = create_list()
                                }
                            }
                            intTextField(IntRange(Consts.Tool_Window_Limits.min_size, Consts.Tool_Window_Limits.max_size)).bindIntText(item::size)
                        }
                        is Tool_Window_Config.Windowed_Tool_Window_Config -> {
                            val s = segmentedButton(listOf("Docked", "Windowed")) {
                                text = it
                                icon = if(it == "Docked")AllIcons.Plugins.Downloads else AllIcons.General.OpenInToolWindow
                            }.apply {
                                selectedItem = "Windowed"
                            }
                            s.whenItemSelected {
                                SwingUtilities.invokeLater {

                                    Plugin_State.instance.data.variants[index] = Tool_Window_Config.Docked_Tool_Window_Config()
                                    list_placeholder.component = create_list()
                                }
                            }
                           panel {
                               row {
                                   intTextField().bindIntText(item::x)
                                   intTextField().bindIntText(item::y)
                                   intTextField().bindIntText(item::width)
                                   intTextField().bindIntText(item::height)
                                   checkBox("Always on top").bindSelected(item::always_on_top)
                               }
                           }
                        }
                    }
                }
            }
        }
    }

    override fun createPanel(): DialogPanel {
        val panel = panel {
            row {
                list_placeholder = placeholder()
            }
            row {
                val button = button("Add Config", { event ->
                    Plugin_State.instance.data.variants.add(Tool_Window_Config.Windowed_Tool_Window_Config())
                    list_placeholder.component = create_list()
                })
                button.component.icon = AllIcons.Actions.AddList
                /*
                                checkBox = checkBox("Allow config per IDE window width")
                                    .bindSelected(Plugin_State.instance.data::per_window_config)
                */
            }
        }
        component = panel
        list_placeholder.component = create_list()
        return panel
    }

}