From e6de9b8cb1f11cc2f2b9ccd09353c5bc5f9dd150 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Tue, 20 Dec 2016 11:58:03 +0100 Subject: [PATCH] =?UTF-8?q?ComponentStoreImpl=20=E2=80=94=20support=20Modi?= =?UTF-8?q?ficationTracker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../intellij/compiler/CompilerTestUtil.java | 11 +- .../src/ComponentStoreImpl.kt | 105 ++++++++++-------- .../src/StateStorageManagerImpl.kt | 1 + .../testSrc/ApplicationStoreTest.kt | 74 ++++++++++-- .../testSrc/ModuleStoreTest.kt | 4 +- .../openapi/module/impl/ModuleImpl.java | 18 ++- .../openapi/components/StateStorage.java | 3 +- .../impl/stores/IComponentStore.java | 2 +- .../openapi/module/impl/ModuleEx.java | 4 + .../roots/impl/ModuleRootManagerImpl.java | 23 +++- .../openapi/roots/impl/RootModelImpl.java | 3 +- .../util/resources/misc/registry.properties | 2 + 12 files changed, 181 insertions(+), 69 deletions(-) diff --git a/java/testFramework/src/com/intellij/compiler/CompilerTestUtil.java b/java/testFramework/src/com/intellij/compiler/CompilerTestUtil.java index 7f6e34084a89..ea56e86b35f8 100644 --- a/java/testFramework/src/com/intellij/compiler/CompilerTestUtil.java +++ b/java/testFramework/src/com/intellij/compiler/CompilerTestUtil.java @@ -19,6 +19,7 @@ import com.intellij.compiler.server.BuildManager; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.ServiceKt; import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.module.Module; @@ -52,19 +53,19 @@ public class CompilerTestUtil { @TestOnly public static void saveApplicationSettings() { EdtTestUtil.runInEdtAndWait(() -> { - doSaveComponent(ProjectJdkTable.getInstance()); - doSaveComponent(FileTypeManager.getInstance()); + doSaveComponent((PersistentStateComponent)ProjectJdkTable.getInstance()); + doSaveComponent((PersistentStateComponent)FileTypeManager.getInstance()); }); } @TestOnly - public static void saveApplicationComponent(final Object appComponent) { + public static void saveApplicationComponent(@NotNull PersistentStateComponent appComponent) { EdtTestUtil.runInEdtAndWait(() -> doSaveComponent(appComponent)); } - private static void doSaveComponent(Object appComponent) { + private static void doSaveComponent(@NotNull PersistentStateComponent component) { //noinspection TestOnlyProblems - ServiceKt.getStateStore(ApplicationManager.getApplication()).saveApplicationComponent(appComponent); + ServiceKt.getStateStore(ApplicationManager.getApplication()).saveApplicationComponent(component); } @TestOnly diff --git a/platform/configuration-store-impl/src/ComponentStoreImpl.kt b/platform/configuration-store-impl/src/ComponentStoreImpl.kt index 86c7043fc48e..b427ebf1685e 100644 --- a/platform/configuration-store-impl/src/ComponentStoreImpl.kt +++ b/platform/configuration-store-impl/src/ComponentStoreImpl.kt @@ -17,7 +17,6 @@ package com.intellij.configurationStore import com.intellij.notification.NotificationsManager import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.PathManager import com.intellij.openapi.application.ex.DecodeDefaultsUtil import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.components.* @@ -27,12 +26,11 @@ import com.intellij.openapi.components.impl.ComponentManagerImpl import com.intellij.openapi.components.impl.stores.* import com.intellij.openapi.components.impl.stores.StateStorageManager.ExternalizationSession import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.diagnostic.debug import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.project.Project -import com.intellij.openapi.util.InvalidDataException -import com.intellij.openapi.util.JDOMExternalizable -import com.intellij.openapi.util.JDOMUtil -import com.intellij.openapi.util.NamedJDOMExternalizable +import com.intellij.openapi.util.* +import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess import com.intellij.project.isDirectoryBased @@ -62,8 +60,10 @@ internal val deprecatedComparator = Comparator { o1, o2 -> w1 - w2 } +private class ComponentInfo(val component: Any, var lastModificationCount: Long) + abstract class ComponentStoreImpl : IComponentStore { - private val components = Collections.synchronizedMap(THashMap()) + private val components = Collections.synchronizedMap(THashMap()) private val settingsSavingComponents = CopyOnWriteArrayList() internal open val project: Project? @@ -87,8 +87,8 @@ abstract class ComponentStoreImpl : IComponentStore { if (component is PersistentStateComponent<*>) { val stateSpec = StoreUtil.getStateSpec(component) componentName = stateSpec.name - doAddComponent(componentName, component) - if (initPersistentComponent(stateSpec, component, null, false) && service) { + val info = doAddComponent(componentName, component) + if (initComponent(stateSpec, component, info, null, false) && service) { // if not service, so, component manager will check it later for all components project?.let { val app = ApplicationManager.getApplication() @@ -132,6 +132,7 @@ abstract class ComponentStoreImpl : IComponentStore { } } + val isUseModificationCount = Registry.`is`("store.save.use.modificationCount", true) val externalizationSession = if (components.isEmpty()) null else storageManager.startExternalization() if (externalizationSession != null) { val names = ArrayUtilRt.toStringArray(components.keys) @@ -142,13 +143,27 @@ abstract class ComponentStoreImpl : IComponentStore { val start = if (timeLog == null) 0 else System.currentTimeMillis() try { - commitComponent(externalizationSession, components.get(name)!!, name) + val info = components.get(name)!! + var currentModificationCount = -1L + + if (info.lastModificationCount >= 0) { + currentModificationCount = (info.component as ModificationTracker).modificationCount + if (currentModificationCount == info.lastModificationCount) { + LOG.debug { "${if (isUseModificationCount) "Skip " else ""}$name: modificationCount ${currentModificationCount} equals to last saved" } + if (isUseModificationCount) { + continue + } + } + } + + commitComponent(externalizationSession, info.component, name) + info.lastModificationCount = currentModificationCount } catch (e: Throwable) { if (errors == null) { errors = SmartList() } - errors!!.add(Exception("Cannot get ${name} component state", e)) + errors!!.add(Exception("Cannot get $name component state", e)) } timeLog?.let { @@ -184,7 +199,7 @@ abstract class ComponentStoreImpl : IComponentStore { CompoundRuntimeException.throwIfNotEmpty(errors) } - override @TestOnly fun saveApplicationComponent(component: Any) { + override @TestOnly fun saveApplicationComponent(component: PersistentStateComponent<*>) { val externalizationSession = storageManager.startExternalization() ?: return commitComponent(externalizationSession, component, null) @@ -193,18 +208,8 @@ abstract class ComponentStoreImpl : IComponentStore { return } - val absolutePath: String - val state = StoreUtil.getStateSpec(component.javaClass) - if (state != null) { - absolutePath = Paths.get(storageManager.expandMacros(findNonDeprecated(state.storages).path)).toAbsolutePath().toString() - } - else if (component is ExportableApplicationComponent && component is NamedJDOMExternalizable) { - absolutePath = PathManager.getOptionsFile(component).absolutePath - } - else { - throw AssertionError("${component.javaClass} doesn't have @State annotation and doesn't implement ExportableApplicationComponent") - } - + val state = StoreUtil.getStateSpec(component.javaClass) ?: throw AssertionError("${component.javaClass} doesn't have @State annotation and doesn't implement ExportableApplicationComponent") + val absolutePath = Paths.get(storageManager.expandMacros(findNonDeprecated(state.storages).path)).toAbsolutePath().toString() runWriteAction { try { VfsRootAccess.allowRootAccess(absolutePath) @@ -262,19 +267,33 @@ abstract class ComponentStoreImpl : IComponentStore { return componentName } - private fun doAddComponent(name: String, component: Any) { - val existing = components.put(name, component) - if (existing != null && existing !== component) { + private fun doAddComponent(name: String, component: Any): ComponentInfo { + val newInfo = ComponentInfo(component, (component as? ModificationTracker)?.modificationCount ?: -1) + val existing = components.put(name, newInfo) + if (existing != null && existing.component !== component) { components.put(name, existing) - LOG.error("Conflicting component name '$name': ${existing.javaClass} and ${component.javaClass}") + LOG.error("Conflicting component name '$name': ${existing.component.javaClass} and ${component.javaClass}") + return existing } + return newInfo } - private fun initPersistentComponent(stateSpec: State, component: PersistentStateComponent, changedStorages: Set?, reloadData: Boolean): Boolean { + private fun initComponent(stateSpec: State, component: PersistentStateComponent, info: ComponentInfo, changedStorages: Set?, reloadData: Boolean): Boolean { if (loadPolicy == StateLoadPolicy.NOT_LOAD) { return false } + if (doInitComponent(stateSpec, component, changedStorages, reloadData)) { + // if component was initialized, update lastModificationCount + if (info.lastModificationCount >= 0) { + info.lastModificationCount = (component as ModificationTracker).modificationCount + } + return true + } + return false + } + + private fun doInitComponent(stateSpec: State, component: PersistentStateComponent, changedStorages: Set?, reloadData: Boolean): Boolean { val name = stateSpec.name val stateClass = ComponentSerializationUtil.getStateClass(component.javaClass) if (!stateSpec.defaultStateAsResource && LOG.isDebugEnabled && getDefaultState(component, name, stateClass) != null) { @@ -298,6 +317,7 @@ abstract class ComponentStoreImpl : IComponentStore { name != "AntConfiguration" && name != "ProjectModuleManager" && name != "FacetManager" && + name != "NewModuleRootManager" /* will be changed only on actual user change, so, to speed up module loading, skip it */ && name != "DeprecatedModuleOptionManager" /* doesn't make sense to check it */ && SystemPropertyUtil.getBoolean("use.loaded.state.as.existing", true)) { (storage as? StorageBaseEx<*>)?.createGetSession(component, name, stateClass) @@ -368,12 +388,15 @@ abstract class ComponentStoreImpl : IComponentStore { override final fun isReloadPossible(componentNames: MutableSet) = !componentNames.any { isNotReloadable(it) } - private fun isNotReloadable(component: Any?) = component != null && (component !is PersistentStateComponent<*> || !StoreUtil.getStateSpec(component).reloadable) + private fun isNotReloadable(name: String): Boolean { + val component = components.get(name)?.component ?: return false + return component !is PersistentStateComponent<*> || !StoreUtil.getStateSpec(component).reloadable + } fun getNotReloadableComponents(componentNames: Collection): Collection { var notReloadableComponents: MutableSet? = null for (componentName in componentNames) { - if (isNotReloadable(components[componentName])) { + if (isNotReloadable(componentName)) { if (notReloadableComponents == null) { notReloadableComponents = LinkedHashSet() } @@ -391,24 +414,18 @@ abstract class ComponentStoreImpl : IComponentStore { override final fun reloadState(componentClass: Class>) { val stateSpec = StoreUtil.getStateSpecOrError(componentClass) - @Suppress("UNCHECKED_CAST") - val component = components[stateSpec.name] as PersistentStateComponent? - if (component != null) { - initPersistentComponent(stateSpec, component, emptySet(), true) + val info = components.get(stateSpec.name) ?: return + (info.component as? PersistentStateComponent<*>)?.let { + initComponent(stateSpec, it, info, emptySet(), true) } } private fun reloadState(componentName: String, changedStorages: Set): Boolean { - @Suppress("UNCHECKED_CAST") - val component = components[componentName] as PersistentStateComponent? - if (component == null) { - return false - } - else { - val changedStoragesEmpty = changedStorages.isEmpty() - initPersistentComponent(StoreUtil.getStateSpec(component), component, if (changedStoragesEmpty) null else changedStorages, changedStoragesEmpty) - return true - } + val info = components.get(componentName) ?: return false + val component = info.component as? PersistentStateComponent<*> ?: return false + val changedStoragesEmpty = changedStorages.isEmpty() + initComponent(StoreUtil.getStateSpec(component), component, info, if (changedStoragesEmpty) null else changedStorages, changedStoragesEmpty) + return true } /** diff --git a/platform/configuration-store-impl/src/StateStorageManagerImpl.kt b/platform/configuration-store-impl/src/StateStorageManagerImpl.kt index 325f8c27769a..1ced57020083 100644 --- a/platform/configuration-store-impl/src/StateStorageManagerImpl.kt +++ b/platform/configuration-store-impl/src/StateStorageManagerImpl.kt @@ -384,6 +384,7 @@ open class StateStorageManagerImpl(private val rootTagName: String, override fun setState(storageSpecs: Array, component: Any, componentName: String, state: Any) { val stateStorageChooser = component as? StateStorageChooserEx for (storageSpec in storageSpecs) { + @Suppress("IfThenToElvis") val resolution = if (stateStorageChooser == null) Resolution.DO else stateStorageChooser.getResolution(storageSpec, StateStorageOperation.WRITE) if (resolution == Resolution.SKIP) { continue diff --git a/platform/configuration-store-impl/testSrc/ApplicationStoreTest.kt b/platform/configuration-store-impl/testSrc/ApplicationStoreTest.kt index b348629c2aa3..d4f5d072bf5b 100644 --- a/platform/configuration-store-impl/testSrc/ApplicationStoreTest.kt +++ b/platform/configuration-store-impl/testSrc/ApplicationStoreTest.kt @@ -17,6 +17,7 @@ package com.intellij.configurationStore import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.* +import com.intellij.openapi.util.SimpleModificationTracker import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream import com.intellij.openapi.vfs.CharsetToolkit import com.intellij.openapi.vfs.refreshVfs @@ -209,14 +210,12 @@ internal class ApplicationStoreTest { } @State(name = "A", storages = arrayOf(Storage("a.xml")), additionalExportFile = "foo") - private open class A : PersistentStateComponent { - data class State(@Attribute var foo: String = "", @Attribute var bar: String = "") - - var options = State() + private open class A : PersistentStateComponent { + var options = TestState() override fun getState() = options - override fun loadState(state: State) { + override fun loadState(state: TestState) { this.options = state } } @@ -229,7 +228,7 @@ internal class ApplicationStoreTest { val component = A() componentStore.initComponent(component, false) - assertThat(component.options).isEqualTo(A.State("old")) + assertThat(component.options).isEqualTo(TestState("old")) saveStore() @@ -243,6 +242,63 @@ internal class ApplicationStoreTest { assertThat(file).hasContent("\n \n") } + @Test fun `modification tracker`() { + testAppConfig.refreshVfs() + + @State(name = "A", storages = arrayOf(Storage("a.xml"))) + open class A : PersistentStateComponent, SimpleModificationTracker() { + var options = TestState() + + var stateCalledCount = 0 + + override fun getState(): TestState { + stateCalledCount++ + return options + } + + override fun loadState(state: TestState) { + this.options = state + } + } + + val component = A() + componentStore.initComponent(component, false) + + assertThat(component.modificationCount).isEqualTo(0) + assertThat(component.stateCalledCount).isEqualTo(0) + + // test that store correctly set last modification count to component modification count on init + saveStore() + assertThat(component.stateCalledCount).isEqualTo(0) + + // change modification count - store will be forced to check changes using serialization and A.getState will be called + component.incModificationCount() + saveStore() + assertThat(component.stateCalledCount).isEqualTo(1) + + // test that store correctly save last modification time and doesn't call our state on next save + saveStore() + assertThat(component.stateCalledCount).isEqualTo(1) + + val componentFile = testAppConfig.resolve("a.xml") + assertThat(componentFile).doesNotExist() + + // update data but "forget" to update modification count + component.options.foo = "new" + + saveStore() + assertThat(componentFile).doesNotExist() + + component.incModificationCount() + saveStore() + assertThat(component.stateCalledCount).isEqualTo(2) + + assertThat(componentFile).hasContent(""" + + + """.trimIndent()) + } + @Test fun `do not check if only format changed for non-roamable storage`() { @State(name = "A", storages = arrayOf(Storage(value = "b.xml", roamingType = RoamingType.DISABLED))) class AWorkspace : A() @@ -253,7 +309,7 @@ internal class ApplicationStoreTest { val component = AWorkspace() componentStore.initComponent(component, false) - assertThat(component.options).isEqualTo(A.State("old")) + assertThat(component.options).isEqualTo(TestState("old")) saveStore() @@ -333,4 +389,6 @@ internal class ApplicationStoreTest { XmlSerializerUtil.copyBean(state, this) } } -} \ No newline at end of file +} + +private data class TestState(@Attribute var foo: String = "", @Attribute var bar: String = "") \ No newline at end of file diff --git a/platform/configuration-store-impl/testSrc/ModuleStoreTest.kt b/platform/configuration-store-impl/testSrc/ModuleStoreTest.kt index 4290a60c3647..964c03838bb5 100644 --- a/platform/configuration-store-impl/testSrc/ModuleStoreTest.kt +++ b/platform/configuration-store-impl/testSrc/ModuleStoreTest.kt @@ -14,11 +14,11 @@ import com.intellij.openapi.roots.impl.storage.ClasspathStorage import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.* +import com.intellij.testFramework.assertions.Assertions.assertThat import com.intellij.util.io.parentSystemIndependentPath import com.intellij.util.io.readText import com.intellij.util.io.systemIndependentPath import gnu.trove.TObjectIntHashMap -import org.assertj.core.api.Assertions.assertThat import org.junit.ClassRule import org.junit.Rule import org.junit.Test @@ -83,7 +83,7 @@ class ModuleStoreTest { moduleFile.createModule().useAndDispose { ModuleRootModificationUtil.addContentRoot(this, moduleFile.parentSystemIndependentPath) saveStore() - assertThat(moduleFile).isRegularFile() + assertThat(moduleFile).isRegularFile assertThat(moduleFile.readText()).startsWith("\n") ClasspathStorage.setStorageType(ModuleRootManager.getInstance(this), "eclipse") diff --git a/platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleImpl.java b/platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleImpl.java index 70efbcb11602..eb1d6e25dee7 100644 --- a/platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleImpl.java +++ b/platform/lang-impl/src/com/intellij/openapi/module/impl/ModuleImpl.java @@ -33,6 +33,7 @@ import com.intellij.openapi.module.impl.scopes.ModuleScopeProviderImpl; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.impl.storage.ClasspathStorage; +import com.intellij.openapi.util.SimpleModificationTracker; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.text.StringUtil; @@ -212,7 +213,10 @@ public class ModuleImpl extends PlatformComponentManagerImpl implements ModuleEx @Override public void setOption(@NotNull String key, @NotNull String value) { - getOptionManager().state.options.put(key, value); + DeprecatedModuleOptionManager manager = getOptionManager(); + if (!value.equals(manager.state.options.put(key, value))) { + manager.incModificationCount(); + } } @NotNull @@ -223,7 +227,10 @@ public class ModuleImpl extends PlatformComponentManagerImpl implements ModuleEx @Override public void clearOption(@NotNull String key) { - getOptionManager().state.options.remove(key); + DeprecatedModuleOptionManager manager = getOptionManager(); + if (manager.state.options.remove(key) != null) { + manager.incModificationCount(); + } } @Override @@ -357,8 +364,13 @@ public class ModuleImpl extends PlatformComponentManagerImpl implements ModuleEx return Extensions.getArea(this).getPicoContainer(); } + @Override + public long getOptionsModificationCount() { + return getOptionManager().getModificationCount(); + } + @State(name = "DeprecatedModuleOptionManager") - static class DeprecatedModuleOptionManager implements PersistentStateComponent { + static class DeprecatedModuleOptionManager extends SimpleModificationTracker implements PersistentStateComponent { static final class State { @Property(surroundWithTag = false) @MapAnnotation(surroundKeyWithTag = false, surroundValueWithTag = false, surroundWithTag = false, entryTagName = "option") diff --git a/platform/projectModel-api/src/com/intellij/openapi/components/StateStorage.java b/platform/projectModel-api/src/com/intellij/openapi/components/StateStorage.java index cd9ed5fad9b1..d6995f1f486a 100644 --- a/platform/projectModel-api/src/com/intellij/openapi/components/StateStorage.java +++ b/platform/projectModel-api/src/com/intellij/openapi/components/StateStorage.java @@ -41,7 +41,8 @@ public interface StateStorage { void analyzeExternalChangesAndUpdateIfNeed(@NotNull Set componentNames); interface ExternalizationSession { - void setState(@Nullable Object component, @NotNull String componentName, @NotNull Object state); + default void setState(@Nullable Object component, @NotNull String componentName, @NotNull Object state) { + } /** * return null if nothing to save diff --git a/platform/projectModel-impl/src/com/intellij/openapi/components/impl/stores/IComponentStore.java b/platform/projectModel-impl/src/com/intellij/openapi/components/impl/stores/IComponentStore.java index ba1c82a3da33..ab2009a2af1d 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/components/impl/stores/IComponentStore.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/components/impl/stores/IComponentStore.java @@ -51,5 +51,5 @@ public interface IComponentStore { void save(@NotNull List> readonlyFiles); @TestOnly - void saveApplicationComponent(@NotNull Object component); + void saveApplicationComponent(@NotNull PersistentStateComponent component); } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/module/impl/ModuleEx.java b/platform/projectModel-impl/src/com/intellij/openapi/module/impl/ModuleEx.java index b1fcaa1f9df4..b5f91cce33c1 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/module/impl/ModuleEx.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/module/impl/ModuleEx.java @@ -37,4 +37,8 @@ public interface ModuleEx extends Module { void rename(String newName); void clearScopesCache(); + + default long getOptionsModificationCount() { + return 0; + } } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ModuleRootManagerImpl.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ModuleRootManagerImpl.java index 2b7daf508271..675c89b8ebd9 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ModuleRootManagerImpl.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ModuleRootManagerImpl.java @@ -24,6 +24,7 @@ import com.intellij.openapi.module.ModifiableModuleModel; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleServiceManager; +import com.intellij.openapi.module.impl.ModuleEx; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.*; @@ -31,7 +32,7 @@ import com.intellij.openapi.roots.ex.ProjectRootManagerEx; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.JDOMExternalizable; -import com.intellij.openapi.util.WriteExternalException; +import com.intellij.openapi.util.ModificationTracker; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; import com.intellij.util.ThrowableRunnable; @@ -45,7 +46,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -public class ModuleRootManagerImpl extends ModuleRootManager implements Disposable { +public class ModuleRootManagerImpl extends ModuleRootManager implements Disposable, ModificationTracker { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.ModuleRootManagerImpl"); private final Module myModule; @@ -57,6 +58,7 @@ public class ModuleRootManagerImpl extends ModuleRootManager implements Disposab private final OrderRootsCache myOrderRootsCache; private final Map myModelCreations = new THashMap<>(); + private volatile long myModificationCount; public ModuleRootManagerImpl(Module module, ProjectRootManagerImpl projectRootManager, @@ -163,9 +165,15 @@ public class ModuleRootManagerImpl extends ModuleRootManager implements Disposab ApplicationManager.getApplication().assertWriteAccessAllowed(); LOG.assertTrue(rootModel.myModuleRootManager == this); + boolean changed = rootModel.isChanged(); + final Project project = myModule.getProject(); final ModifiableModuleModel moduleModel = ModuleManager.getInstance(project).getModifiableModel(); ModifiableModelCommitter.multiCommit(new ModifiableRootModel[]{rootModel}, moduleModel); + + if (changed) { + myModificationCount++; + } } static void doCommit(RootModelImpl rootModel) { @@ -344,6 +352,15 @@ public class ModuleRootManagerImpl extends ModuleRootManager implements Disposab } } + @Override + public long getModificationCount() { + long result = myModificationCount; + if (myModule instanceof ModuleEx) { + result += ((ModuleEx)myModule).getOptionsModificationCount(); + } + return result; + } + public static class ModuleRootManagerState implements JDOMExternalizable { private RootModelImpl myRootModel; private Element myRootModelElement; @@ -361,7 +378,7 @@ public class ModuleRootManagerImpl extends ModuleRootManager implements Disposab } @Override - public void writeExternal(Element element) throws WriteExternalException { + public void writeExternal(Element element) { myRootModel.writeExternal(element); } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootModelImpl.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootModelImpl.java index 8d9e4a8f1b4b..5ba384fd32aa 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootModelImpl.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootModelImpl.java @@ -29,7 +29,6 @@ import com.intellij.openapi.roots.libraries.LibraryTable; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; import com.intellij.util.ArrayUtil; @@ -408,7 +407,7 @@ public class RootModelImpl extends RootModelBase implements ModifiableRootModel return e; } - public void writeExternal(@NotNull Element element) throws WriteExternalException { + public void writeExternal(@NotNull Element element) { for (ModuleExtension extension : myExtensions) { extension.writeExternal(element); } diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index db78c1b066f6..dae1d4eb8314 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -910,6 +910,8 @@ typing.freeze.report.dumps=false typing.freeze.report.dumps.description=Automatically reports thread dumps to our statistics server store.basedir.parent.detection=true +store.save.use.modificationCount=true + ide.ui.composite.editor.for.combobox.description=Allows to use composite components based on JPanel as a ComboBox editor ide.ui.composite.editor.for.combobox=true