project configuration dsl: state reader

GitOrigin-RevId: d477fde05a6722e60c277ee53eced3cea6b41772
This commit is contained in:
Vladimir Krivosheev
2019-05-05 08:54:40 +02:00
committed by intellij-monorepo-bot
parent 3590278d34
commit bb260c8522
26 changed files with 300 additions and 126 deletions

View File

@@ -1,18 +1,15 @@
// Copyright 2000-2018 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.
// Copyright 2000-2019 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.
package com.intellij.configurationStore
import com.intellij.openapi.components.PersistentStateComponentWithModificationTracker
import com.intellij.openapi.components.RoamingType
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.components.*
import com.intellij.openapi.util.ModificationTracker
import com.intellij.util.ThreeState
import java.util.concurrent.TimeUnit
internal fun createComponentInfo(component: Any, stateSpec: State?): ComponentInfo {
internal fun createComponentInfo(component: Any, stateSpec: State?, serviceDescriptor: ServiceDescriptor?): ComponentInfo {
return when (component) {
is ModificationTracker -> ComponentWithModificationTrackerInfo(component, stateSpec)
is PersistentStateComponentWithModificationTracker<*> -> ComponentWithStateModificationTrackerInfo(component, stateSpec!!)
is ModificationTracker -> ComponentWithModificationTrackerInfo(component, stateSpec, serviceDescriptor?.configurationSchemaKey)
is PersistentStateComponentWithModificationTracker<*> -> ComponentWithStateModificationTrackerInfo(component, stateSpec!!, serviceDescriptor?.configurationSchemaKey)
else -> {
val componentInfo = ComponentInfoImpl(component, stateSpec)
if (stateSpec != null && !stateSpec.storages.isEmpty() && stateSpec.storages.all(::isUseSaveThreshold)) {
@@ -27,7 +24,10 @@ private fun isUseSaveThreshold(storage: Storage): Boolean {
return storage.useSaveThreshold != ThreeState.NO && getEffectiveRoamingType(storage.roamingType, storage.path) === RoamingType.DISABLED
}
internal abstract class ComponentInfo {
abstract class ComponentInfo {
open val configurationSchemaKey: String?
get() = null
abstract val component: Any
abstract val stateSpec: State?
@@ -38,6 +38,8 @@ internal abstract class ComponentInfo {
var lastSaved: Int = -1
var affectedPropertyNames: List<String> = emptyList()
open fun updateModificationCount(newCount: Long = currentModificationCount) {
}
}
@@ -63,7 +65,8 @@ private abstract class ModificationTrackerAwareComponentInfo : ComponentInfo() {
}
private class ComponentWithStateModificationTrackerInfo(override val component: PersistentStateComponentWithModificationTracker<*>,
override val stateSpec: State) : ModificationTrackerAwareComponentInfo() {
override val stateSpec: State,
override val configurationSchemaKey: String?) : ModificationTrackerAwareComponentInfo() {
override val currentModificationCount: Long
get() = component.stateModificationCount
@@ -71,7 +74,8 @@ private class ComponentWithStateModificationTrackerInfo(override val component:
}
private class ComponentWithModificationTrackerInfo(override val component: ModificationTracker,
override val stateSpec: State?) : ModificationTrackerAwareComponentInfo() {
override val stateSpec: State?,
override val configurationSchemaKey: String?) : ModificationTrackerAwareComponentInfo() {
override val currentModificationCount: Long
get() = component.modificationCount

View File

@@ -36,6 +36,7 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.jdom.Element
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.CalledInAwt
import org.jetbrains.annotations.TestOnly
import java.io.IOException
@@ -74,10 +75,11 @@ internal fun setRoamableComponentSaveThreshold(thresholdInSeconds: Int) {
NOT_ROAMABLE_COMPONENT_SAVE_THRESHOLD = thresholdInSeconds
}
@ApiStatus.Internal
abstract class ComponentStoreImpl : IComponentStore {
private val components = Collections.synchronizedMap(THashMap<String, ComponentInfo>())
internal open val project: Project?
open val project: Project?
get() = null
open val loadPolicy: StateLoadPolicy
@@ -87,12 +89,12 @@ abstract class ComponentStoreImpl : IComponentStore {
internal fun getComponents(): Map<String, ComponentInfo> = components
override fun initComponent(component: Any, isService: Boolean) {
override fun initComponent(component: Any, serviceDescriptor: ServiceDescriptor?) {
var componentName = ""
try {
@Suppress("DEPRECATION")
if (component is PersistentStateComponent<*>) {
componentName = initPersistenceStateComponent(component, getStateSpec(component), isService)
componentName = initPersistenceStateComponent(component, getStateSpec(component), serviceDescriptor)
}
else if (component is com.intellij.openapi.util.JDOMExternalizable) {
componentName = ComponentManagerImpl.getComponentName(component)
@@ -110,13 +112,13 @@ abstract class ComponentStoreImpl : IComponentStore {
override fun initPersistencePlainComponent(component: Any, key: String) {
initPersistenceStateComponent(PersistenceStateAdapter(component),
StateAnnotation(key, FileStorageAnnotation(StoragePathMacros.WORKSPACE_FILE, false)),
isService = false)
serviceDescriptor = null)
}
private fun initPersistenceStateComponent(component: PersistentStateComponent<*>, stateSpec: State, isService: Boolean): String {
private fun initPersistenceStateComponent(component: PersistentStateComponent<*>, stateSpec: State, serviceDescriptor: ServiceDescriptor?): String {
val componentName = stateSpec.name
val info = doAddComponent(componentName, component, stateSpec)
if (initComponent(info, null, ThreeState.NO) && isService) {
val info = doAddComponent(componentName, component, stateSpec, serviceDescriptor)
if (initComponent(info, null, ThreeState.NO) && serviceDescriptor != null) {
// if not service, so, component manager will check it later for all components
project?.let {
val app = ApplicationManager.getApplication()
@@ -170,6 +172,7 @@ abstract class ComponentStoreImpl : IComponentStore {
// well, strictly speaking each component saving takes some time, but +/- several seconds doesn't matter
val nowInSeconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()).toInt()
val isSaveModLogEnabled = SAVE_MOD_LOG.isDebugEnabled && !ApplicationManager.getApplication().isUnitTestMode
for (name in names) {
val start = System.currentTimeMillis()
try {
@@ -191,7 +194,10 @@ abstract class ComponentStoreImpl : IComponentStore {
info.lastSaved = nowInSeconds
}
else {
SAVE_MOD_LOG.debug { "Skip $name: was already saved in last ${TimeUnit.SECONDS.toMinutes(NOT_ROAMABLE_COMPONENT_SAVE_THRESHOLD_DEFAULT.toLong())} minutes (lastSaved ${info.lastSaved}, now: $nowInSeconds)" }
if (isSaveModLogEnabled) {
SAVE_MOD_LOG.debug("Skip $name: was already saved in last ${TimeUnit.SECONDS.toMinutes(NOT_ROAMABLE_COMPONENT_SAVE_THRESHOLD_DEFAULT.toLong())} minutes" +
" (lastSaved ${info.lastSaved}, now: $nowInSeconds)")
}
continue
}
}
@@ -259,11 +265,15 @@ abstract class ComponentStoreImpl : IComponentStore {
return
}
val state = (component as PersistentStateComponent<*>).state ?: return
var state: Any? = null
// state can be null, so, we cannot compare to null to check is state was requested or not
var stateRequested = false
val stateSpec = info.stateSpec!!
val effectiveComponentName = componentName ?: stateSpec.name
val stateStorageChooser = component as? StateStorageChooserEx
val storageSpecs = getStorageSpecs(component, stateSpec, StateStorageOperation.WRITE)
@Suppress("UNCHECKED_CAST")
val storageSpecs = getStorageSpecs(component as PersistentStateComponent<Any>, stateSpec, StateStorageOperation.WRITE)
for (storageSpec in storageSpecs) {
@Suppress("IfThenToElvis")
var resolution = if (stateStorageChooser == null) Resolution.DO else stateStorageChooser.getResolution(storageSpec, StateStorageOperation.WRITE)
@@ -280,12 +290,28 @@ abstract class ComponentStoreImpl : IComponentStore {
}
}
session.getProducer(storage)?.setState(component, effectiveComponentName, if (storageSpec.deprecated || resolution == Resolution.CLEAR) null else state)
val sessionProducer = session.getProducer(storage) ?: continue
if (storageSpec.deprecated || resolution == Resolution.CLEAR) {
sessionProducer.setState(component, effectiveComponentName, null)
}
else {
if (!stateRequested) {
stateRequested = true
state = (info.component as PersistentStateComponent<*>).state
}
setStateToSaveSessionProducer(state, info, effectiveComponentName, sessionProducer)
}
}
}
// method is not called if storage is deprecated or clear was requested (state in these cases is null), but called if state is null if returned so from component
protected open fun setStateToSaveSessionProducer(state: Any?, info: ComponentInfo, effectiveComponentName: String, sessionProducer: SaveSessionProducer) {
sessionProducer.setState(info.component, effectiveComponentName, state)
}
private fun initJdomExternalizable(@Suppress("DEPRECATION") component: com.intellij.openapi.util.JDOMExternalizable, componentName: String): String? {
doAddComponent(componentName, component, null)
doAddComponent(componentName, component, stateSpec = null, serviceDescriptor = null)
if (loadPolicy != StateLoadPolicy.LOAD) {
return null
@@ -312,8 +338,8 @@ abstract class ComponentStoreImpl : IComponentStore {
return componentName
}
private fun doAddComponent(name: String, component: Any, stateSpec: State?): ComponentInfo {
val newInfo = createComponentInfo(component, stateSpec)
private fun doAddComponent(name: String, component: Any, stateSpec: State?, serviceDescriptor: ServiceDescriptor?): ComponentInfo {
val newInfo = createComponentInfo(component, stateSpec, serviceDescriptor)
val existing = components.put(name, newInfo)
if (existing != null && existing.component !== component) {
components.put(name, existing)
@@ -324,23 +350,21 @@ abstract class ComponentStoreImpl : IComponentStore {
}
private fun initComponent(info: ComponentInfo, changedStorages: Set<StateStorage>?, reloadData: ThreeState): Boolean {
if (loadPolicy == StateLoadPolicy.NOT_LOAD) {
return false
return when {
loadPolicy == StateLoadPolicy.NOT_LOAD -> false
doInitComponent(info, changedStorages, reloadData) -> {
// if component was initialized, update lastModificationCount
info.updateModificationCount()
true
}
else -> false
}
@Suppress("UNCHECKED_CAST")
if (doInitComponent(info.stateSpec!!, info.component as PersistentStateComponent<Any>, changedStorages, reloadData)) {
// if component was initialized, update lastModificationCount
info.updateModificationCount()
return true
}
return false
}
private fun doInitComponent(stateSpec: State,
component: PersistentStateComponent<Any>,
changedStorages: Set<StateStorage>?,
reloadData: ThreeState): Boolean {
private fun doInitComponent(info: ComponentInfo, changedStorages: Set<StateStorage>?, reloadData: ThreeState): Boolean {
val stateSpec = info.stateSpec!!
@Suppress("UNCHECKED_CAST")
val component = info.component as PersistentStateComponent<Any>
val name = stateSpec.name
@Suppress("UNCHECKED_CAST")
val stateClass: Class<Any> = when (component) {
@@ -360,7 +384,11 @@ abstract class ComponentStoreImpl : IComponentStore {
}
val storage = storageManager.getStateStorage(storageSpec)
val stateGetter = doCreateStateGetter(reloadData, changedStorages, storage, stateSpec, name, component, stateClass)
// if storage marked as changed, it means that analyzeExternalChangesAndUpdateIfNeed was called for it and storage is already reloaded
val isReloadDataForStorage = if (reloadData == ThreeState.UNSURE) changedStorages!!.contains(storage) else reloadData.toBoolean()
val stateGetter = doCreateStateGetter(isReloadDataForStorage, storage, info, name, stateClass)
var state = stateGetter.getState(defaultState)
if (state == null) {
if (changedStorages != null && changedStorages.contains(storage)) {
@@ -393,24 +421,21 @@ abstract class ComponentStoreImpl : IComponentStore {
return true
}
private fun doCreateStateGetter(reloadData: ThreeState,
changedStorages: Set<StateStorage>?,
storage: StateStorage,
stateSpec: State,
name: String,
component: PersistentStateComponent<Any>,
stateClass: Class<Any>): StateGetter<Any> {
// if storage marked as changed, it means that analyzeExternalChangesAndUpdateIfNeed was called for it and storage is already reloaded
val isReloadDataForStorage = if (reloadData == ThreeState.UNSURE) changedStorages!!.contains(storage) else reloadData.toBoolean()
protected open fun doCreateStateGetter(reloadData: Boolean,
storage: StateStorage,
info: ComponentInfo,
name: String,
stateClass: Class<Any>): StateGetter<Any> {
// use.loaded.state.as.existing used in upsource
val isUseLoadedStateAsExisting = stateSpec.useLoadedStateAsExisting
&& isUseLoadedStateAsExisting(storage)
&& SystemProperties.getBooleanProperty("use.loaded.state.as.existing", true)
return createStateGetter(isUseLoadedStateAsExisting, storage, component, name, stateClass, reloadData = isReloadDataForStorage)
val isUseLoadedStateAsExisting = info.stateSpec!!.useLoadedStateAsExisting && isUseLoadedStateAsExisting(storage)
@Suppress("UNCHECKED_CAST")
return createStateGetter(isUseLoadedStateAsExisting, storage, info.component as PersistentStateComponent<Any>, name, stateClass, reloadData)
}
protected open fun isUseLoadedStateAsExisting(storage: StateStorage) = (storage as? XmlElementStorage)?.roamingType != RoamingType.DISABLED
protected open fun isUseLoadedStateAsExisting(storage: StateStorage): Boolean {
return (storage as? XmlElementStorage)?.roamingType != RoamingType.DISABLED
&& SystemProperties.getBooleanProperty("use.loaded.state.as.existing", true)
}
protected open fun getPathMacroManagerForDefaults(): PathMacroManager? = null

View File

@@ -1,6 +1,7 @@
// Copyright 2000-2019 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.
package com.intellij.configurationStore
import com.intellij.openapi.components.ServiceDescriptor
import com.intellij.openapi.components.SettingsSavingComponent
import com.intellij.openapi.diagnostic.runAndLogException
import com.intellij.openapi.extensions.ExtensionPointName
@@ -36,7 +37,7 @@ abstract class ComponentStoreWithExtraComponents : ComponentStoreImpl() {
}
}
override fun initComponent(component: Any, isService: Boolean) {
override fun initComponent(component: Any, serviceDescriptor: ServiceDescriptor?) {
@Suppress("DEPRECATION")
if (component is com.intellij.configurationStore.SettingsSavingComponent) {
asyncSettingsSavingComponents.add(component)
@@ -45,7 +46,7 @@ abstract class ComponentStoreWithExtraComponents : ComponentStoreImpl() {
settingsSavingComponents.add(component)
}
super.initComponent(component, isService)
super.initComponent(component, serviceDescriptor)
}
internal suspend fun saveSettingsSavingComponentsAndCommitComponents(result: SaveResult, forceSavingAllSettings: Boolean): SaveSessionProducerManager {

View File

@@ -24,6 +24,7 @@ import com.intellij.util.io.*
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.CalledInAny
import java.nio.file.AccessDeniedException
import java.nio.file.Path
@@ -32,7 +33,8 @@ import java.nio.file.Paths
internal val IProjectStore.nameFile: Path
get() = Paths.get(directoryStorePath, ProjectImpl.NAME_FILE)
private open class ProjectStoreImpl(project: Project) : ProjectStoreBase(project) {
@ApiStatus.Internal
open class ProjectStoreImpl(project: Project) : ProjectStoreBase(project) {
private var lastSavedProjectName: String? = null
init {
@@ -145,7 +147,8 @@ private open class ProjectStoreImpl(project: Project) : ProjectStoreBase(project
}
}
private class ProjectWithModulesStoreImpl(project: Project) : ProjectStoreImpl(project) {
@ApiStatus.Internal
open class ProjectWithModulesStoreImpl(project: Project) : ProjectStoreImpl(project) {
override suspend fun saveModules(errors: MutableList<Throwable>, isForceSavingAllSettings: Boolean): List<SaveSession> {
val modules = ModuleManager.getInstance(project)?.modules ?: Module.EMPTY_ARRAY
if (modules.isEmpty()) {

View File

@@ -31,7 +31,7 @@ abstract class SaveSessionBase : SaveSessionProducer, SafeWriteRequestor, LargeF
setSerializedState(componentName, element)
}
protected abstract fun setSerializedState(componentName: String, element: Element?)
abstract fun setSerializedState(componentName: String, element: Element?)
}
internal fun serializeState(state: Any): Element? {

View File

@@ -9,6 +9,7 @@ import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.util.JDOMUtil
import com.intellij.util.isEmpty
import org.jdom.Element
import org.jetbrains.annotations.ApiStatus
abstract class StorageBaseEx<T : Any> : StateStorageBase<T>() {
internal fun <S : Any> createGetSession(component: PersistentStateComponent<S>, componentName: String, stateClass: Class<S>, reload: Boolean = false): StateGetter<S> {
@@ -31,13 +32,12 @@ internal fun <S : Any> createStateGetter(isUseLoadedStateAsExisting: Boolean, st
return storage.getState(component, componentName, stateClass, mergeInto, reloadData)
}
override fun archiveState() : S? {
return null
}
override fun archiveState(): S? = null
}
}
internal interface StateGetter<S : Any> {
@ApiStatus.Internal
interface StateGetter<S : Any> {
fun getState(mergeInto: S? = null): S?
fun archiveState(): S?

View File

@@ -68,7 +68,7 @@ internal class ApplicationStoreTest {
componentStore.storageManager.removeStreamProvider(MyStreamProvider::class.java)
componentStore.storageManager.addStreamProvider(streamProvider)
componentStore.initComponent(component, false)
componentStore.initComponent(component, null)
component.foo = "newValue"
componentStore.save()
@@ -87,7 +87,7 @@ internal class ApplicationStoreTest {
val storageManager = componentStore.storageManager
storageManager.removeStreamProvider(MyStreamProvider::class.java)
storageManager.addStreamProvider(streamProvider)
componentStore.initComponent(component, false)
componentStore.initComponent(component, null)
assertThat(component.foo).isEqualTo("newValue")
assertThat(Paths.get(storageManager.expandMacros(fileSpec))).doesNotExist()
@@ -116,7 +116,7 @@ internal class ApplicationStoreTest {
testAppConfig.refreshVfs()
componentStore.initComponent(component, false)
componentStore.initComponent(component, null)
assertThat(component.foo).isEqualTo("new")
component.foo = "new2"
@@ -152,7 +152,7 @@ internal class ApplicationStoreTest {
testAppConfig.refreshVfs()
val component = A()
componentStore.initComponent(component, false)
componentStore.initComponent(component, null)
component.options.foo = "new"
@@ -203,7 +203,7 @@ internal class ApplicationStoreTest {
testAppConfig.refreshVfs()
val component = SeveralStoragesConfigured()
componentStore.initComponent(component, false)
componentStore.initComponent(component, null)
assertThat(component.foo).isEqualTo("new")
componentStore.save()
@@ -221,7 +221,7 @@ internal class ApplicationStoreTest {
testAppConfig.refreshVfs()
val component = A()
componentStore.initComponent(component, false)
componentStore.initComponent(component, null)
assertThat(component.options).isEqualTo(TestState("old"))
componentStore.save()
@@ -247,12 +247,12 @@ internal class ApplicationStoreTest {
val component = A()
component.isThrowErrorOnLoadState = true
assertThatThrownBy {
componentStore.initComponent(component, false)
componentStore.initComponent(component, null)
}.isInstanceOf(ProcessCanceledException::class.java)
assertThat(component.options).isEqualTo(TestState())
component.isThrowErrorOnLoadState = false
componentStore.initComponent(component, false)
componentStore.initComponent(component, null)
assertThat(component.options).isEqualTo(TestState("old"))
}
@@ -266,7 +266,7 @@ internal class ApplicationStoreTest {
testAppConfig.refreshVfs()
val component = AWorkspace()
componentStore.initComponent(component, false)
componentStore.initComponent(component, null)
assertThat(component.options).isEqualTo(TestState("old"))
try {
@@ -289,7 +289,7 @@ internal class ApplicationStoreTest {
class AOther : A()
val component = AOther()
componentStore.initComponent(component, false)
componentStore.initComponent(component, null)
component.options.foo = "old"
componentStore.save()
@@ -312,15 +312,15 @@ internal class ApplicationStoreTest {
class COther : A()
val component = AOther()
componentStore.initComponent(component, false)
componentStore.initComponent(component, null)
component.options.foo = "old"
val component2 = BOther()
componentStore.initComponent(component2, false)
componentStore.initComponent(component2, null)
component2.options.foo = "old?"
val component3 = COther()
componentStore.initComponent(component3, false)
componentStore.initComponent(component3, null)
component3.options.bar = "foo"
componentStore.save()

View File

@@ -61,7 +61,7 @@ internal class ComponentStoreModificationTrackerTest {
}
val component = A()
componentStore.initComponent(component, false)
componentStore.initComponent(component, null)
assertThat(component.modificationCount).isEqualTo(0)
assertThat(component.stateCalledCount.get()).isEqualTo(0)
@@ -128,7 +128,7 @@ internal class ComponentStoreModificationTrackerTest {
}
val component = A()
componentStore.initComponent(component, false)
componentStore.initComponent(component, null)
assertThat(component.modificationCount.get()).isEqualTo(0)
assertThat(component.stateCalledCount.get()).isEqualTo(0)

View File

@@ -84,12 +84,12 @@ internal class DefaultProjectStoreTest {
<main name="$TEST_COMPONENT_NAME"/><sub name="foo" /><sub name="bar" />
</component>""".trimIndent()))
val stateStore = ProjectManager.getInstance().defaultProject.stateStore as ComponentStoreImpl
stateStore.initComponent(defaultTestComponent, true)
stateStore.initComponent(defaultTestComponent, null)
try {
// obviously, project must be directory-based also
createProjectAndUseInLoadComponentStateMode(tempDirManager, directoryBased = true) {
val component = TestComponent()
it.stateStore.initComponent(component, true)
it.stateStore.initComponent(component, null)
assertThat(component.state).isEqualTo(defaultTestComponent.state)
}
}

View File

@@ -202,7 +202,7 @@ internal class ProjectStoreTest {
class AOther : A()
val component = AOther()
componentStore.initComponent(component, false)
componentStore.initComponent(component, null)
assertThat(component.options.foo).isEqualTo("some data")
componentStore.save()
@@ -237,7 +237,7 @@ internal class ProjectStoreTest {
private suspend fun test(project: Project): TestComponent {
val testComponent = TestComponent()
project.stateStore.initComponent(testComponent, true)
project.stateStore.initComponent(testComponent, null)
assertThat(testComponent.state).isEqualTo(TestState("customValue"))
testComponent.state!!.value = "foo"

View File

@@ -1,26 +1,15 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2019 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.
package com.intellij.openapi.components.ex;
import com.intellij.openapi.components.ComponentManager;
import com.intellij.openapi.components.ServiceDescriptor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author max
*/
public interface ComponentManagerEx extends ComponentManager {
void initializeComponent(@NotNull Object component, boolean service);
default void initializeComponent(@NotNull Object component, @Nullable ServiceDescriptor serviceDescriptor) {
}
}

View File

@@ -216,10 +216,6 @@ public abstract class ComponentManagerImpl extends UserDataHolderBase implements
return ProgressManager.getInstance().getProgressIndicator();
}
@Override
public void initializeComponent(@NotNull Object component, boolean service) {
}
protected void handleInitComponentError(@NotNull Throwable ex, String componentClassName, PluginId pluginId) {
LOG.error(ex);
}
@@ -511,7 +507,7 @@ public abstract class ComponentManagerImpl extends UserDataHolderBase implements
indicator.checkCanceled();
setProgressDuringInit(indicator);
}
initializeComponent(instance, false);
initializeComponent(instance, null);
if (instance instanceof BaseComponent) {
((BaseComponent)instance).initComponent();
}

View File

@@ -39,9 +39,9 @@ public abstract class PlatformComponentManagerImpl extends ComponentManagerImpl
}
@Override
public void initializeComponent(@NotNull Object component, boolean service) {
if (!service || !(component instanceof PathMacroManager || component instanceof IComponentStore)) {
getComponentStore().initComponent(component, service);
public void initializeComponent(@NotNull Object component, @Nullable ServiceDescriptor serviceDescriptor) {
if (serviceDescriptor == null || !(component instanceof PathMacroManager || component instanceof IComponentStore)) {
getComponentStore().initComponent(component, serviceDescriptor);
}
}

View File

@@ -244,7 +244,7 @@ public final class ServiceManagerImpl implements Disposable {
Disposer.register(myComponentManager, (Disposable)instance);
}
myComponentManager.initializeComponent(instance, true);
myComponentManager.initializeComponent(instance, myDescriptor);
ParallelActivity.SERVICE.record(startTime, instance.getClass(), DefaultPicoContainer.getActivityLevel(container));
return instance;
}

View File

@@ -44,18 +44,20 @@ internal class KotlinAwareBeanBinding(beanClass: Class<*>, accessor: MutableAcce
}
}
private fun serializeBaseStateInto(o: BaseState, _element: Element?, filter: SerializationFilter?): Element? {
fun serializeBaseStateInto(o: BaseState, _element: Element?, filter: SerializationFilter?, excludedPropertyNames: Collection<String>? = null): Element? {
var element = _element
// order of bindings must be used, not order of properties
var bindingIndices: IntArrayList? = null
for (property in o.__getProperties()) {
if (property.isEqualToDefault()) {
val propertyName = property.name!!
if (property.isEqualToDefault() || (excludedPropertyNames != null && excludedPropertyNames.contains(propertyName))) {
continue
}
val propertyBindingIndex = findBindingIndex(property.name!!)
val propertyBindingIndex = findBindingIndex(propertyName)
if (propertyBindingIndex < 0) {
logger<BaseState>().debug("cannot find binding for property ${property.name}")
logger<BaseState>().debug("cannot find binding for property ${propertyName}")
continue
}

View File

@@ -61,6 +61,11 @@ fun <T : Any> T.serialize(filter: SerializationFilter? = getDefaultSerialization
}
}
fun deserializeBaseStateWithCustomNameFilter(state: BaseState, excludedPropertyNames: Collection<String>): Element? {
val binding = serializer.getClassBinding(state.javaClass) as KotlinAwareBeanBinding
return binding.serializeBaseStateInto(state, null, getDefaultSerializationFilter(), excludedPropertyNames)
}
inline fun <reified T: Any> Element.deserialize(): T = deserialize(T::class.java)
fun <T> Element.deserialize(clazz: Class<T>): T {

View File

@@ -4,6 +4,7 @@ package com.intellij.openapi.components.impl.stores
import com.intellij.configurationStore.SaveSession
import com.intellij.configurationStore.StateStorageManager
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.ServiceDescriptor
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.messages.MessageBus
import org.jetbrains.annotations.SystemIndependent
@@ -14,7 +15,7 @@ interface IComponentStore {
fun setPath(path: @SystemIndependent String)
fun initComponent(component: Any, isService: Boolean)
fun initComponent(component: Any, serviceDescriptor: ServiceDescriptor?)
fun initPersistencePlainComponent(component: Any, key: String)

View File

@@ -24,5 +24,6 @@
<orderEntry type="module" module-name="intellij.java.testFramework" scope="TEST" />
<orderEntry type="library" scope="TEST" name="gson" level="project" />
<orderEntry type="module" module-name="intellij.yaml" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.configurationStore.impl" />
</component>
</module>

View File

@@ -7,11 +7,6 @@
]]>
</description>
<!--<depends>org.jetbrains.kotlin</depends>-->
<!--<extensions defaultExtensionNs="org.jetbrains.kotlin">-->
<!--<scriptDefinitionContributor implementation="com.intellij.configurationScript.ConfigurationScriptContributor" order="first"/>-->
<!--</extensions>-->
<extensions defaultExtensionNs="JavaScript.JsonSchema">
<ProviderFactory implementation="com.intellij.configurationScript.IntellijConfigurationJsonSchemaProviderFactory"/>
</extensions>
@@ -20,5 +15,9 @@
<runConfigurationTemplateProvider implementation="com.intellij.configurationScript.providers.MyRunConfigurationTemplateProvider"/>
<updateSettingsProvider implementation="com.intellij.configurationScript.providers.MyUpdateSettingsProvider"/>
<applicationService serviceInterface="com.intellij.openapi.project.impl.ProjectStoreFactory"
serviceImplementation="com.intellij.configurationScript.providers.ConfigurationScriptProjectStoreFactory"
overrides="true"/>
</extensions>
</idea-plugin>

View File

@@ -1,5 +1,6 @@
package com.intellij.configurationScript
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
@@ -11,6 +12,7 @@ import com.intellij.util.concurrency.SynchronizedClearableLazy
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.io.inputStreamIfExists
import org.yaml.snakeyaml.nodes.MappingNode
import org.yaml.snakeyaml.nodes.ScalarNode
import org.yaml.snakeyaml.parser.ParserImpl
import org.yaml.snakeyaml.reader.StreamReader
import java.io.Reader
@@ -38,6 +40,10 @@ internal class ConfigurationFileManager(project: Project) {
registerClearableLazyValue(yamlData)
}
companion object {
fun getInstance(project: Project) = project.service<ConfigurationFileManager>()
}
fun registerClearableLazyValue(value: SynchronizedClearableLazy<*>) {
clearableLazyValues.add(value)
}
@@ -74,6 +80,27 @@ internal class ConfigurationFileManager(project: Project) {
}
fun getConfigurationNode() = yamlData.value
fun findValueNode(namePath: String): MappingNode? {
return findValueNodeByPath(namePath, yamlData.value ?: return null)
}
}
internal fun findValueNodeByPath(namePath: String, rootNode: MappingNode): MappingNode? {
var node = rootNode
loop@
for (name in namePath.splitToSequence('.')) {
for (tuple in node.value) {
val keyNode = tuple.keyNode
if (keyNode is ScalarNode && keyNode.value == name) {
node = tuple.valueNode as? MappingNode ?: continue
continue@loop
}
}
return null
}
return if (node === rootNode) null else node
}
internal fun doRead(reader: Reader): MappingNode? {

View File

@@ -4,11 +4,12 @@ import com.intellij.configurationStore.properties.CollectionStoredProperty
import com.intellij.configurationStore.properties.MapStoredProperty
import com.intellij.openapi.components.BaseState
import com.intellij.openapi.components.ScalarProperty
import com.intellij.openapi.components.StoredProperty
import org.yaml.snakeyaml.nodes.MappingNode
import org.yaml.snakeyaml.nodes.ScalarNode
import org.yaml.snakeyaml.nodes.SequenceNode
internal fun readObject(instance: BaseState, node: MappingNode): BaseState {
internal fun <T : BaseState> readIntoObject(instance: T, node: MappingNode, affectedPropertyConsumer: ((StoredProperty<Any>) -> Unit)? = null): T {
val properties = instance.__getProperties()
for (tuple in node.value) {
val valueNode = tuple.valueNode
@@ -17,6 +18,7 @@ internal fun readObject(instance: BaseState, node: MappingNode): BaseState {
for (property in properties) {
if (property is ScalarProperty && property.jsonType.isScalar && key == property.name) {
property.parseAndSetValue(valueNode.value)
affectedPropertyConsumer?.invoke(property)
break
}
}
@@ -25,6 +27,7 @@ internal fun readObject(instance: BaseState, node: MappingNode): BaseState {
for (property in properties) {
if (property is MapStoredProperty<*, *> && key == property.name) {
readMap(property, valueNode)
affectedPropertyConsumer?.invoke(property)
break
}
}
@@ -33,6 +36,7 @@ internal fun readObject(instance: BaseState, node: MappingNode): BaseState {
for (property in properties) {
if (property is CollectionStoredProperty<*, *> && key == property.name) {
readCollection(property, valueNode)
affectedPropertyConsumer?.invoke(property)
break
}
}

View File

@@ -0,0 +1,77 @@
package com.intellij.configurationScript.providers
import com.intellij.configurationScript.ConfigurationFileManager
import com.intellij.configurationScript.readIntoObject
import com.intellij.configurationStore.*
import com.intellij.openapi.components.BaseState
import com.intellij.openapi.components.StateStorage
import com.intellij.openapi.components.impl.stores.IComponentStore
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.impl.ProjectStoreFactory
import com.intellij.util.ReflectionUtil
import com.intellij.util.concurrency.SynchronizedClearableLazy
import org.yaml.snakeyaml.nodes.MappingNode
internal class ConfigurationScriptProjectStoreFactory : ProjectStoreFactory {
override fun createStore(project: Project): IComponentStore {
return if (project.isDefault) DefaultProjectStoreImpl(project) else MyProjectStore(project)
}
}
private class MyProjectStore(project: Project) : ProjectWithModulesStoreImpl(project) {
val data by lazy {
val result = SynchronizedClearableLazy {
val node = ConfigurationFileManager.getInstance(project).getConfigurationNode() ?: return@SynchronizedClearableLazy null
readPluginsConfiguration(node)
}
ConfigurationFileManager.getInstance(project).registerClearableLazyValue(result)
result
}
override fun doCreateStateGetter(reloadData: Boolean,
storage: StateStorage,
info: ComponentInfo,
name: String,
stateClass: Class<Any>): StateGetter<Any> {
val stateGetter = super.doCreateStateGetter(reloadData, storage, info, name, stateClass)
val configurationSchemaKey = info.configurationSchemaKey ?: return stateGetter
val node = ConfigurationFileManager.getInstance(project).findValueNode(configurationSchemaKey) ?: return stateGetter
return object : StateGetter<Any> {
override fun getState(mergeInto: Any?): Any? {
var state = stateGetter.getState(mergeInto)
if (state == null) {
state = ReflectionUtil.newInstance(stateClass, false)
}
val affectedProperties = mutableListOf<String>()
readIntoObject(state as BaseState, node) { affectedProperties.add(it.name!!) }
info.affectedPropertyNames = affectedProperties
return state
}
override fun archiveState(): Any? {
// feature "preventing inappropriate state modification" is disabled for workspace components,
// also, this feature makes little sense for properly implemented PersistenceStateComponent using BaseState
return null
}
}
}
// In general this method is not required in this form, because SaveSessionBase.setState accepts serialized state (Element) without any side-effects or performance degradation,
// but it is better to express contract in code to make sure that it will be not broken in the future.
override fun setStateToSaveSessionProducer(state: Any?, info: ComponentInfo, effectiveComponentName: String, sessionProducer: SaveSessionProducer) {
val configurationSchemaKey = info.configurationSchemaKey
if (state == null || configurationSchemaKey == null || info.affectedPropertyNames.isEmpty() || sessionProducer !is SaveSessionBase) {
super.setStateToSaveSessionProducer(state, info, effectiveComponentName, sessionProducer)
}
else {
val serializedState = deserializeBaseStateWithCustomNameFilter(state as BaseState, info.affectedPropertyNames)
sessionProducer.setSerializedState(effectiveComponentName, serializedState)
}
}
}
internal fun <T : BaseState> readComponentConfiguration(node: MappingNode, stateClass: Class<out T>): T? {
return readIntoObject(ReflectionUtil.newInstance(stateClass), node)
}

View File

@@ -2,7 +2,7 @@ package com.intellij.configurationScript.providers
import com.intellij.configurationScript.Keys
import com.intellij.configurationScript.LOG
import com.intellij.configurationScript.readObject
import com.intellij.configurationScript.readIntoObject
import com.intellij.configurationScript.schemaGenerators.processConfigurationTypes
import com.intellij.configurationScript.schemaGenerators.rcFactoryIdToPropertyName
import com.intellij.execution.configurations.ConfigurationFactory
@@ -116,6 +116,6 @@ internal class RunConfigurationListReader(private val processor: (factory: Confi
// very important - set BEFORE read to ensure that user can set any value for isAllowRunningInParallel and it will be not overridden by us later
instance.isAllowRunningInParallel = factory.singletonPolicy.isAllowRunningInParallel
}
processor(factory, readObject(instance, node))
processor(factory, readIntoObject(instance, node))
}
}

View File

@@ -2,9 +2,8 @@ package com.intellij.configurationScript.providers
import com.intellij.configurationScript.ConfigurationFileManager
import com.intellij.configurationScript.Keys
import com.intellij.configurationScript.readObject
import com.intellij.configurationScript.readIntoObject
import com.intellij.openapi.components.BaseState
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.processOpenedProjects
import com.intellij.openapi.updateSettings.impl.UpdateSettingsProvider
@@ -16,10 +15,10 @@ import org.yaml.snakeyaml.nodes.ScalarNode
private val dataKey = NotNullLazyKey.create<SynchronizedClearableLazy<PluginsConfiguration?>, Project>("MyUpdateSettingsProvider") { project ->
val data = SynchronizedClearableLazy {
val node = project.service<ConfigurationFileManager>().getConfigurationNode() ?: return@SynchronizedClearableLazy null
val node = ConfigurationFileManager.getInstance(project).getConfigurationNode() ?: return@SynchronizedClearableLazy null
readPluginsConfiguration(node)
}
project.service<ConfigurationFileManager>().registerClearableLazyValue(data)
ConfigurationFileManager.getInstance(project).registerClearableLazyValue(data)
data
}
@@ -46,7 +45,7 @@ internal fun readPluginsConfiguration(rootNode: MappingNode): PluginsConfigurati
val keyNode = tuple.keyNode
if (keyNode is ScalarNode && keyNode.value == Keys.plugins) {
val valueNode = tuple.valueNode as? MappingNode ?: continue
return readObject(PluginsConfiguration(), valueNode) as PluginsConfiguration
return readIntoObject(PluginsConfiguration(), valueNode)
}
}
return null

View File

@@ -0,0 +1,40 @@
package com.intellij.configurationScript
import com.intellij.configurationScript.providers.readComponentConfiguration
import com.intellij.openapi.components.BaseState
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.assertions.Assertions.assertThat
import org.intellij.lang.annotations.Language
import org.junit.ClassRule
import org.junit.Test
class ComponentStateTest {
companion object {
@JvmField
@ClassRule
val projectRule = ProjectRule()
}
@Test
fun read() {
val result = doReadComponentConfiguration("versionControl.git", """
versionControl:
git:
updateMethod: rebase
""")
assertThat(result!!.updateMethod).isEqualTo(UpdateMethod.REBASE)
}
}
@Suppress("SameParameterValue")
private fun doReadComponentConfiguration(namePath: String, @Language("YAML") data: String): TestState? {
return readComponentConfiguration(findValueNodeByPath(namePath, doRead(data.trimIndent().reader())!!)!!, TestState::class.java)
}
private class TestState : BaseState() {
var updateMethod by enum(UpdateMethod.BRANCH_DEFAULT)
}
private enum class UpdateMethod {
BRANCH_DEFAULT, MERGE, REBASE
}

View File

@@ -6,6 +6,7 @@ import org.junit.runners.Suite
@RunWith(Suite::class)
@Suite.SuiteClasses(
ConfigurationFileTest::class,
ComponentStateTest::class,
ConfigurationSchemaTest::class,
PropertyValueReaderTest::class
)