From 713f9a1c1ebad8b6f5403dabda24e0bd2b487cff Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Sat, 11 Oct 2014 20:02:12 +0200 Subject: [PATCH] =?UTF-8?q?DirectoryBasedStorage=20=E2=80=94=20fix=20analy?= =?UTF-8?q?zeExternalChangesAndUpdateIfNeed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../roots/impl/storage/ClasspathStorage.java | 3 +- .../impl/stores/ComponentStoreImpl.java | 39 +-- .../impl/stores/DefaultProjectStoreImpl.java | 2 +- .../impl/stores/DefaultsStateStorage.java | 4 +- .../impl/stores/DirectoryBasedStorage.java | 245 ++++++++---------- .../impl/stores/FileBasedStorage.java | 10 +- .../impl/stores/IComponentStore.java | 3 +- .../components/impl/stores/StorageUtil.java | 1 - .../impl/stores/XmlElementStorage.java | 55 +--- .../components/store/StateStorageBase.java | 53 ++++ .../project/impl/ProjectManagerImpl.java | 21 +- .../impl/XmlElementStorageTest.java | 2 +- .../openapi/components/StateStorage.java | 3 +- .../impl/stores/DirectoryStorageData.java | 84 +++--- .../impl/libraries/ProjectLibraryTable.java | 11 +- .../com/intellij/mock/MockProjectStore.java | 4 +- 16 files changed, 245 insertions(+), 295 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/openapi/components/store/StateStorageBase.java diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/storage/ClasspathStorage.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/storage/ClasspathStorage.java index c7b934a420fc..ec6a42365c45 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/impl/storage/ClasspathStorage.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/storage/ClasspathStorage.java @@ -28,7 +28,6 @@ import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.ModuleRootModel; import com.intellij.openapi.roots.impl.ModuleRootManagerImpl; import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.util.io.FileUtil; @@ -176,7 +175,7 @@ public class ClasspathStorage implements StateStorage { } @Override - public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Collection> changedFiles, @NotNull Set result) { + public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Collection changedFiles, @NotNull Set result) { } public void save() throws StateStorageException { diff --git a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/ComponentStoreImpl.java b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/ComponentStoreImpl.java index b5104a662e6a..8a17c7f8948f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/ComponentStoreImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/ComponentStoreImpl.java @@ -27,6 +27,7 @@ import com.intellij.openapi.components.StateStorage.SaveSession; import com.intellij.openapi.components.impl.ComponentManagerImpl; import com.intellij.openapi.components.store.ComponentSaveSession; import com.intellij.openapi.components.store.ReadOnlyModificationException; +import com.intellij.openapi.components.store.StateStorageBase; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.project.Project; @@ -39,6 +40,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ArrayUtilRt; import com.intellij.util.ReflectionUtil; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; import com.intellij.util.containers.SmartHashSet; import com.intellij.util.messages.MessageBus; import gnu.trove.THashMap; @@ -428,14 +430,14 @@ public abstract class ComponentStoreImpl implements IComponentStore.Reloadable { @Override @Nullable - public final Collection reload(@NotNull Collection> changedFiles) { + public final Collection reload(@NotNull MultiMap changedStorages) { Set componentNames = new SmartHashSet(); - for (Pair pair : changedFiles) { - StateStorage storage = pair.second; + + for (StateStorage storage : changedStorages.keySet()) { try { // we must update (reload in-memory storage data) even if non-reloadable component will be detected later // not saved -> user does own modification -> new (on disk) state will be overwritten and not applied - storage.analyzeExternalChangesAndUpdateIfNeed(changedFiles, componentNames); + storage.analyzeExternalChangesAndUpdateIfNeed(changedStorages.get(storage), componentNames); } catch (Throwable e) { LOG.error(e); @@ -475,13 +477,18 @@ public abstract class ComponentStoreImpl implements IComponentStore.Reloadable { } @NotNull - public static ReloadComponentStoreStatus reloadStore(@NotNull Collection> causes, @NotNull IComponentStore.Reloadable store) { + public static ReloadComponentStoreStatus reloadStore(@NotNull Collection> changedStorages, @NotNull IComponentStore.Reloadable store) { + MultiMap storageToFiles = MultiMap.createLinkedSet(); + for (Pair pair : changedStorages) { + storageToFiles.putValue(pair.second, pair.first); + } + Collection notReloadableComponents; boolean willBeReloaded = false; try { AccessToken token = WriteAction.start(); try { - notReloadableComponents = store.reload(causes); + notReloadableComponents = store.reload(storageToFiles); } catch (Throwable e) { Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()), @@ -496,14 +503,14 @@ public abstract class ComponentStoreImpl implements IComponentStore.Reloadable { return ReloadComponentStoreStatus.SUCCESS; } - willBeReloaded = askToRestart(store, notReloadableComponents, causes); + willBeReloaded = askToRestart(store, notReloadableComponents, changedStorages); return willBeReloaded ? ReloadComponentStoreStatus.RESTART_AGREED : ReloadComponentStoreStatus.RESTART_CANCELLED; } finally { if (!willBeReloaded) { - for (Pair cause : causes) { - if (cause.second instanceof XmlElementStorage) { - ((XmlElementStorage)cause.second).enableSaving(); + for (StateStorage storage : storageToFiles.keySet()) { + if (storage instanceof StateStorageBase) { + ((StateStorageBase)storage).enableSaving(); } } } @@ -513,7 +520,7 @@ public abstract class ComponentStoreImpl implements IComponentStore.Reloadable { // used in settings repository plugin public static boolean askToRestart(@NotNull Reloadable store, @NotNull Collection notReloadableComponents, - @Nullable Collection> causes) { + @Nullable Collection> changedStorages) { StringBuilder message = new StringBuilder(); String storeName = store instanceof IApplicationStore ? "Application" : "Project"; message.append(storeName).append(' '); @@ -540,11 +547,11 @@ public abstract class ComponentStoreImpl implements IComponentStore.Reloadable { if (Messages.showYesNoDialog(message.toString(), storeName + " Files Changed", Messages.getQuestionIcon()) == Messages.YES) { - if (causes != null) { - for (Pair cause : causes) { - StateStorage stateStorage = cause.getSecond(); - if (stateStorage instanceof XmlElementStorage) { - ((XmlElementStorage)stateStorage).disableSaving(); + if (changedStorages != null) { + for (Pair cause : changedStorages) { + StateStorage storage = cause.getSecond(); + if (storage instanceof StateStorageBase) { + ((StateStorageBase)storage).disableSaving(); } } } diff --git a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/DefaultProjectStoreImpl.java b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/DefaultProjectStoreImpl.java index 3c1bf6f964ce..c1b06007420b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/DefaultProjectStoreImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/DefaultProjectStoreImpl.java @@ -61,7 +61,7 @@ public class DefaultProjectStoreImpl extends ProjectStoreImpl { ComponentManager componentManager = getComponentManager(); final Element element = _d; - final XmlElementStorage storage = new XmlElementStorage("", RoamingType.DISABLED, PathMacroManager.getInstance(componentManager).createTrackingSubstitutor(), componentManager, + final XmlElementStorage storage = new XmlElementStorage("", RoamingType.DISABLED, PathMacroManager.getInstance(componentManager).createTrackingSubstitutor(), ROOT_TAG_NAME, null, ComponentVersionProvider.EMPTY) { @Override diff --git a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/DefaultsStateStorage.java b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/DefaultsStateStorage.java index 080f5019c470..972ca370966b 100644 --- a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/DefaultsStateStorage.java +++ b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/DefaultsStateStorage.java @@ -20,7 +20,6 @@ import com.intellij.openapi.components.PathMacroManager; import com.intellij.openapi.components.StateStorage; import com.intellij.openapi.components.StateStorageException; import com.intellij.openapi.util.JDOMUtil; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.xmlb.JDOMXIncluder; import org.jdom.Document; @@ -38,7 +37,6 @@ import java.util.Set; class DefaultsStateStorage implements StateStorage { private final PathMacroManager myPathMacroManager; - public DefaultsStateStorage(@Nullable final PathMacroManager pathMacroManager) { myPathMacroManager = pathMacroManager; } @@ -92,6 +90,6 @@ class DefaultsStateStorage implements StateStorage { } @Override - public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Collection> changedFiles, @NotNull Set result) { + public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Collection changedFiles, @NotNull Set result) { } } diff --git a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/DirectoryBasedStorage.java b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/DirectoryBasedStorage.java index b12e93f59787..3bbf0e33cdd5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/DirectoryBasedStorage.java +++ b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/DirectoryBasedStorage.java @@ -16,21 +16,20 @@ package com.intellij.openapi.components.impl.stores; import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.AccessToken; +import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.components.*; -import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.components.store.StateStorageBase; import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.WriteExternalException; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.util.text.StringUtilRt; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileAdapter; import com.intellij.openapi.vfs.VirtualFileEvent; import com.intellij.openapi.vfs.tracker.VirtualFileTracker; import com.intellij.util.containers.SmartHashSet; +import gnu.trove.TObjectObjectProcedure; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -42,74 +41,63 @@ import java.util.Set; //todo: support missing plugins //todo: support storage data -public class DirectoryBasedStorage implements StateStorage, Disposable { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.components.impl.stores.DirectoryBasedStorage"); - - private final TrackingPathMacroSubstitutor myPathMacroSubstitutor; +public class DirectoryBasedStorage extends StateStorageBase { private final File myDir; private final StateSplitter mySplitter; - private final FileTypeManager myFileTypeManager; - private DirectoryStorageData myStorageData = null; + private DirectoryStorageData myStorageData; public DirectoryBasedStorage(@Nullable TrackingPathMacroSubstitutor pathMacroSubstitutor, @NotNull String dir, @NotNull StateSplitter splitter, @NotNull Disposable parentDisposable, @Nullable final Listener listener) { - myPathMacroSubstitutor = pathMacroSubstitutor; + super(pathMacroSubstitutor); + myDir = new File(dir); mySplitter = splitter; - Disposer.register(parentDisposable, this); VirtualFileTracker virtualFileTracker = ServiceManager.getService(VirtualFileTracker.class); if (virtualFileTracker != null && listener != null) { - final String path = myDir.getAbsolutePath(); - final String fileUrl = LocalFileSystem.PROTOCOL_PREFIX + path.replace(File.separatorChar, '/'); - virtualFileTracker.addTracker(fileUrl, new VirtualFileAdapter() { + virtualFileTracker.addTracker(LocalFileSystem.PROTOCOL_PREFIX + myDir.getAbsolutePath().replace(File.separatorChar, '/'), new VirtualFileAdapter() { @Override - public void contentsChanged(@NotNull final VirtualFileEvent event) { - if (!StringUtilRt.endsWithIgnoreCase(event.getFile().getNameSequence(), ".xml")) return; - assert listener != null; - listener.storageFileChanged(event, DirectoryBasedStorage.this); + public void contentsChanged(@NotNull VirtualFileEvent event) { + notifyIfNeed(event); } @Override - public void fileDeleted(@NotNull final VirtualFileEvent event) { - if (!StringUtilRt.endsWithIgnoreCase(event.getFile().getNameSequence(), ".xml")) return; - assert listener != null; - listener.storageFileChanged(event, DirectoryBasedStorage.this); + public void fileDeleted(@NotNull VirtualFileEvent event) { + notifyIfNeed(event); } @Override - public void fileCreated(@NotNull final VirtualFileEvent event) { - if (!StringUtilRt.endsWithIgnoreCase(event.getFile().getNameSequence(), ".xml")) return; - assert listener != null; - listener.storageFileChanged(event, DirectoryBasedStorage.this); + public void fileCreated(@NotNull VirtualFileEvent event) { + notifyIfNeed(event); } - }, false, this); + + private void notifyIfNeed(@NotNull VirtualFileEvent event) { + // storage directory will be removed if the only child was removed + if (event.getFile().isDirectory() || DirectoryStorageData.isStorageFile(event.getFile())) { + listener.storageFileChanged(event, DirectoryBasedStorage.this); + } + } + }, false, parentDisposable); } - - myFileTypeManager = FileTypeManager.getInstance(); } @Override - public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Collection> changedFiles, @NotNull Set result) { - boolean containsSelf = false; - for (Pair pair : changedFiles) { - if (pair.second == this && StringUtilRt.endsWithIgnoreCase(pair.first.getNameSequence(), ".xml")) { - containsSelf = true; - break; - } - } - - if (!containsSelf) { - return; - } - + public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Collection changedFiles, @NotNull Set result) { // todo reload only changed file, compute diff - myStorageData = loadState(); - result.addAll(myStorageData.getComponentNames()); + DirectoryStorageData oldData = myStorageData; + DirectoryStorageData newData = loadState(); + myStorageData = newData; + if (oldData == null) { + result.addAll(newData.getComponentNames()); + } + else { + result.addAll(oldData.getComponentNames()); + result.addAll(newData.getComponentNames()); + } } @Override @@ -122,7 +110,6 @@ public class DirectoryBasedStorage implements StateStorage, Disposable { if (!myStorageData.containsComponent(componentName)) { return DefaultStateSerializer.deserializeState(new Element(StorageData.COMPONENT), stateClass, mergeInto); } - return myStorageData.getMergedState(componentName, stateClass, mySplitter, mergeInto); } @@ -133,8 +120,9 @@ public class DirectoryBasedStorage implements StateStorage, Disposable { } @Override - public boolean hasState(@Nullable final Object component, @NotNull String componentName, final Class aClass, final boolean reloadData) { - if (!myDir.exists()) { + public boolean hasState(@Nullable Object component, @NotNull String componentName, Class aClass, boolean reloadData) { + // dir could be deleted on VCS update: storage data is empty and dir doesn't exists - we must return true to reload component + if (myStorageData == null && !myDir.exists()) { return false; } if (reloadData) { @@ -144,105 +132,24 @@ public class DirectoryBasedStorage implements StateStorage, Disposable { } @Override - @NotNull + @Nullable public ExternalizationSession startExternalization() { - if (myStorageData == null) { - try { - myStorageData = loadState(); - } - catch (StateStorageException e) { - LOG.error(e); - } - } - return new MyExternalizationSession(myStorageData.clone()); + return checkIsSavingDisabled() ? null : new MySaveSession(this); } @Nullable @Override public SaveSession startSave(@NotNull ExternalizationSession externalizationSession) { - return new MySaveSession(((MyExternalizationSession)externalizationSession).myStorageData, myPathMacroSubstitutor); + return checkIsSavingDisabled() ? null : (MySaveSession)externalizationSession; } - @Override - public void dispose() { - } - - private class MySaveSession implements SaveSession { - private final DirectoryStorageData myStorageData; - private final TrackingPathMacroSubstitutor myPathMacroSubstitutor; - - private MySaveSession(@NotNull DirectoryStorageData storageData, @Nullable TrackingPathMacroSubstitutor pathMacroSubstitutor) { - myStorageData = storageData; - myPathMacroSubstitutor = pathMacroSubstitutor; - } - - @Override - public void save() throws StateStorageException { - final Set currentNames = new SmartHashSet(); - File[] children = myDir.listFiles(); - if (children != null) { - for (File child : children) { - final String fileName = child.getName(); - if (!myFileTypeManager.isFileIgnored(fileName) && StringUtil.endsWithIgnoreCase(fileName, ".xml")) { - currentNames.add(fileName); - } - } - } - - myStorageData.process(new DirectoryStorageData.StorageDataProcessor() { - @Override - public void process(final String componentName, final File file, final Element element) { - currentNames.remove(file.getName()); - - if (myPathMacroSubstitutor != null) { - myPathMacroSubstitutor.collapsePaths(element); - } - - if (file.lastModified() <= myStorageData.getLastTimeStamp()) { - StorageUtil.save(file, element, MySaveSession.this, false, LocalFileSystem.getInstance().findFileByIoFile(file)); - myStorageData.updateLastTimestamp(file); - } - } - }); - - if (myDir.exists() && !currentNames.isEmpty()) { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - if (myDir.exists()) { - for (String name : currentNames) { - File child = new File(myDir, name); - if (child.lastModified() > myStorageData.getLastTimeStamp()) { - // do not touch new files during VC update (which aren't read yet) - // now got an opposite problem: file is recreated if was removed by VC during update. - return; - } - - final VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByIoFile(child); - if (virtualFile != null) { - try { - LOG.debug("Removing configuration file: " + virtualFile.getPresentableUrl()); - virtualFile.delete(MySaveSession.this); - } - catch (IOException e) { - LOG.error(e); - } - } - } - } - } - }); - } - - myStorageData.clear(); - } - } - - private class MyExternalizationSession implements ExternalizationSession { + private static class MySaveSession implements SaveSession, ExternalizationSession { + private final DirectoryBasedStorage myStorage; private final DirectoryStorageData myStorageData; - private MyExternalizationSession(final DirectoryStorageData storageData) { - myStorageData = storageData; + private MySaveSession(@NotNull DirectoryBasedStorage storage) { + myStorage = storage; + myStorageData = storage.myStorageData == null ? storage.loadState() : storage.myStorageData.clone(); } @Override @@ -260,7 +167,7 @@ public class DirectoryBasedStorage implements StateStorage, Disposable { } if (element != null) { - for (Pair pair : mySplitter.splitState(element)) { + for (Pair pair : myStorage.mySplitter.splitState(element)) { Element e = pair.first; String name = pair.second; @@ -268,9 +175,67 @@ public class DirectoryBasedStorage implements StateStorage, Disposable { statePart.setAttribute(StorageData.NAME, componentName); statePart.addContent(e.detach()); - myStorageData.put(componentName, new File(myDir, name), statePart, false); + myStorageData.put(componentName, new File(myStorage.myDir, name), statePart, false); } } } + + @Override + public void save() { + final VirtualFile dir = LocalFileSystem.getInstance().findFileByIoFile(myStorage.myDir); + final Set existingFileNames = new SmartHashSet(); + for (String componentName : myStorageData.getComponentNames()) { + myStorageData.processComponent(componentName, new TObjectObjectProcedure() { + @Override + public boolean execute(File file, Element element) { + String fileName = file.getName(); + existingFileNames.add(fileName); + + if (myStorage.myPathMacroSubstitutor != null) { + myStorage.myPathMacroSubstitutor.collapsePaths(element); + } + + if (file.lastModified() <= myStorageData.getLastTimeStamp()) { + StorageUtil.save(file, element, MySaveSession.this, false, dir == null ? null : dir.findChild(fileName)); + myStorageData.updateLastTimestamp(file); + } + + return true; + } + }); + } + + if (dir != null && dir.exists()) { + FileTypeManager fileTypeManager = FileTypeManager.getInstance(); + AccessToken token = WriteAction.start(); + try { + for (VirtualFile file : dir.getChildren()) { + String fileName = file.getName(); + if (fileTypeManager.isFileIgnored(fileName) || !DirectoryStorageData.isStorageFile(file) || existingFileNames.contains(fileName)) { + continue; + } + + if (file.getTimeStamp() > myStorageData.getLastTimeStamp()) { + // do not touch new files during VC update (which aren't read yet) + // now got an opposite problem: file is recreated if was removed by VC during update. + return; + } + + try { + LOG.debug("Removing configuration file: " + file.getPresentableUrl()); + file.delete(this); + } + catch (IOException e) { + LOG.error(e); + } + } + } + finally { + token.finish(); + } + } + + myStorage.myStorageData = myStorageData; + } } } diff --git a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/FileBasedStorage.java b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/FileBasedStorage.java index 1b12e4db75d6..5fae82cdf75e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/FileBasedStorage.java +++ b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/FileBasedStorage.java @@ -56,7 +56,7 @@ public class FileBasedStorage extends XmlElementStorage { @Nullable final Listener listener, @Nullable StreamProvider streamProvider, ComponentVersionProvider componentVersionProvider) { - super(fileSpec, roamingType, pathMacroManager, parentDisposable, rootElementName, streamProvider, componentVersionProvider); + super(fileSpec, roamingType, pathMacroManager, rootElementName, streamProvider, componentVersionProvider); myFilePath = filePath; myFile = new File(filePath); @@ -82,12 +82,10 @@ public class FileBasedStorage extends XmlElementStorage { @Override public void contentsChanged(@NotNull final VirtualFileEvent event) { - if (!isDisposed()) { - assert listener != null; - listener.storageFileChanged(event, FileBasedStorage.this); - } + assert listener != null; + listener.storageFileChanged(event, FileBasedStorage.this); } - }, false, this); + }, false, parentDisposable); } } } diff --git a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/IComponentStore.java b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/IComponentStore.java index 892401cba08c..ab5064904516 100644 --- a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/IComponentStore.java +++ b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/IComponentStore.java @@ -20,6 +20,7 @@ import com.intellij.openapi.components.StateStorageException; import com.intellij.openapi.components.store.ComponentSaveSession; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -63,6 +64,6 @@ public interface IComponentStore { * list of not reloadable components (reload is not performed) */ @Nullable - Collection reload(@NotNull Collection> changedFiles); + Collection reload(@NotNull MultiMap changedStorages); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StorageUtil.java b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StorageUtil.java index 30dde0ad011d..3c3af756e715 100644 --- a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StorageUtil.java +++ b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StorageUtil.java @@ -324,7 +324,6 @@ public class StorageUtil { } } - @SuppressWarnings("Contract") @Nullable public static Document loadDocument(@Nullable InputStream stream) { if (stream == null) { diff --git a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/XmlElementStorage.java b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/XmlElementStorage.java index f018f9f5c7bb..e896f00daaf5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/XmlElementStorage.java +++ b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/XmlElementStorage.java @@ -15,11 +15,12 @@ */ package com.intellij.openapi.components.impl.stores; -import com.intellij.openapi.Disposable; import com.intellij.openapi.components.*; -import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.components.store.StateStorageBase; import com.intellij.openapi.options.CurrentUserHolder; -import com.intellij.openapi.util.*; +import com.intellij.openapi.util.JDOMExternalizable; +import com.intellij.openapi.util.JDOMUtil; +import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.ContainerUtil; @@ -37,49 +38,38 @@ import java.io.IOException; import java.io.InputStream; import java.util.*; -public abstract class XmlElementStorage implements StateStorage, Disposable { - protected static final Logger LOG = Logger.getInstance(XmlElementStorage.class); - +public abstract class XmlElementStorage extends StateStorageBase { private static final String ATTR_NAME = "name"; private static final String VERSION_FILE_SUFFIX = ".ver"; - protected TrackingPathMacroSubstitutor myPathMacroSubstitutor; @NotNull protected final String myRootElementName; protected StorageData myLoadedData; protected final StreamProvider myStreamProvider; protected final String myFileSpec; protected boolean myBlockSavingTheContent = false; - private boolean mySavingDisabled = false; private final ComponentVersionProvider myLocalVersionProvider; protected final RemoteComponentVersionProvider myRemoteVersionProvider; protected final RoamingType myRoamingType; - private boolean myDisposed; - protected XmlElementStorage(@NotNull String fileSpec, @Nullable RoamingType roamingType, @Nullable TrackingPathMacroSubstitutor pathMacroSubstitutor, - @NotNull Disposable parentDisposable, @NotNull String rootElementName, @Nullable StreamProvider streamProvider, ComponentVersionProvider componentVersionProvider) { + super(pathMacroSubstitutor); + myFileSpec = fileSpec; myRoamingType = roamingType == null ? RoamingType.PER_USER : roamingType; - myPathMacroSubstitutor = pathMacroSubstitutor; myRootElementName = rootElementName; myStreamProvider = myRoamingType == RoamingType.DISABLED ? null : streamProvider; - Disposer.register(parentDisposable, this); myLocalVersionProvider = componentVersionProvider; myRemoteVersionProvider = streamProvider == null || !streamProvider.isVersioningRequired() ? null : new RemoteComponentVersionProvider(); } - protected boolean isDisposed() { - return myDisposed; - } - @Nullable protected abstract Element loadLocalData(); @@ -170,19 +160,13 @@ public abstract class XmlElementStorage implements StateStorage, Disposable { @Override @Nullable public final ExternalizationSession startExternalization() { - if (LOG.isDebugEnabled() && myFileSpec.equals(StoragePathMacros.MODULE_FILE)) { - LOG.debug("startExternalization: mySavingDisabled " + mySavingDisabled + " for " + toString()); - } - return mySavingDisabled ? null : createSaveSession(getStorageData()); + return checkIsSavingDisabled() ? null : createSaveSession(getStorageData()); } @Nullable @Override public SaveSession startSave(@NotNull ExternalizationSession externalizationSession) { - if (mySavingDisabled) { - if (LOG.isDebugEnabled() && myFileSpec.equals(StoragePathMacros.MODULE_FILE)) { - LOG.debug("startSave: saving disabled for " + toString()); - } + if (checkIsSavingDisabled()) { return null; } else { @@ -196,20 +180,6 @@ public abstract class XmlElementStorage implements StateStorage, Disposable { protected abstract XmlElementStorageSaveSession createSaveSession(@NotNull StorageData storageData); - public void disableSaving() { - if (LOG.isDebugEnabled()) { - LOG.debug("Saving disabled for " + toString()); - } - mySavingDisabled = true; - } - - public void enableSaving() { - if (LOG.isDebugEnabled()) { - LOG.debug("Saving enabled for " + toString()); - } - mySavingDisabled = false; - } - @Nullable protected final Element getElement(@NotNull StorageData data, boolean collapsePaths, @NotNull Map newLiveStates) { Element element = data.save(newLiveStates); @@ -230,7 +200,7 @@ public abstract class XmlElementStorage implements StateStorage, Disposable { } @Override - public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Collection> changedFiles, @NotNull Set result) { + public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Collection changedFiles, @NotNull Set result) { StorageData oldData = myLoadedData; StorageData newData = getStorageData(true); if (oldData == null) { @@ -357,11 +327,6 @@ public abstract class XmlElementStorage implements StateStorage, Disposable { return result; } - @Override - public void dispose() { - myDisposed = true; - } - @TestOnly public void resetData() { myLoadedData = null; diff --git a/platform/platform-impl/src/com/intellij/openapi/components/store/StateStorageBase.java b/platform/platform-impl/src/com/intellij/openapi/components/store/StateStorageBase.java new file mode 100644 index 000000000000..5aa3dd498abc --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/components/store/StateStorageBase.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2014 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. + */ +package com.intellij.openapi.components.store; + +import com.intellij.openapi.components.StateStorage; +import com.intellij.openapi.components.TrackingPathMacroSubstitutor; +import com.intellij.openapi.diagnostic.Logger; +import org.jetbrains.annotations.Nullable; + +public abstract class StateStorageBase implements StateStorage { + protected static final Logger LOG = Logger.getInstance(StateStorageBase.class); + + private boolean mySavingDisabled = false; + protected final TrackingPathMacroSubstitutor myPathMacroSubstitutor; + + protected StateStorageBase(@Nullable TrackingPathMacroSubstitutor trackingPathMacroSubstitutor) { + myPathMacroSubstitutor = trackingPathMacroSubstitutor; + } + + public final void disableSaving() { + if (LOG.isDebugEnabled()) { + LOG.debug("Disabled saving for " + toString()); + } + mySavingDisabled = true; + } + + public final void enableSaving() { + if (LOG.isDebugEnabled()) { + LOG.debug("Enabled saving " + toString()); + } + mySavingDisabled = false; + } + + protected final boolean checkIsSavingDisabled() { + if (mySavingDisabled && LOG.isDebugEnabled()) { + LOG.debug("Saving disabled for " + toString()); + } + return mySavingDisabled; + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java index b0da609dec7b..0cb3872aa745 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java @@ -31,6 +31,7 @@ import com.intellij.openapi.application.impl.ApplicationImpl; import com.intellij.openapi.components.*; import com.intellij.openapi.components.impl.stores.*; import com.intellij.openapi.components.impl.stores.ComponentStoreImpl.ReloadComponentStoreStatus; +import com.intellij.openapi.components.store.StateStorageBase; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.progress.*; @@ -198,7 +199,7 @@ public class ProjectManagerImpl extends ProjectManagerEx implements PersistentSt private void projectStorageFileChanged(@NotNull VirtualFileEvent event, @NotNull StateStorage storage, @Nullable Project project) { VirtualFile file = event.getFile(); - if (!StorageUtil.isChangedByStorageOrSaveSession(event) && !file.isDirectory() && !(event.getRequestor() instanceof ProjectManagerImpl)) { + if (!StorageUtil.isChangedByStorageOrSaveSession(event) && !(event.getRequestor() instanceof ProjectManagerImpl)) { registerProjectToReload(project, file, storage); } } @@ -686,9 +687,7 @@ public class ProjectManagerImpl extends ProjectManagerEx implements PersistentSt if (causes.isEmpty()) { return false; } - - ReloadComponentStoreStatus status = ComponentStoreImpl.reloadStore(causes, ((ProjectEx)project).getStateStore()); - return status == ReloadComponentStoreStatus.RESTART_AGREED; + return ComponentStoreImpl.reloadStore(causes, ((ProjectEx)project).getStateStore()) == ReloadComponentStoreStatus.RESTART_AGREED; } @Override @@ -751,20 +750,20 @@ public class ProjectManagerImpl extends ProjectManagerEx implements PersistentSt } } - private void registerProjectToReload(@Nullable Project project, @NotNull VirtualFile cause, @NotNull StateStorage storage) { + private void registerProjectToReload(@Nullable Project project, @NotNull VirtualFile file, @NotNull StateStorage storage) { if (LOG.isDebugEnabled()) { - LOG.debug("[RELOAD] Registering project to reload: " + cause, new Exception()); + LOG.debug("[RELOAD] Registering project to reload: " + file, new Exception()); } if (project == null) { - myChangedApplicationFiles.add(Pair.create(cause, storage)); + myChangedApplicationFiles.add(Pair.create(file, storage)); } - else if (cause.exists()) { - myChangedProjectFiles.putValue(project, Pair.create(cause, storage)); + else { + myChangedProjectFiles.putValue(project, Pair.create(file, storage)); } - if (storage instanceof XmlElementStorage) { - ((XmlElementStorage)storage).disableSaving(); + if (storage instanceof StateStorageBase) { + ((StateStorageBase)storage).disableSaving(); } if (isReloadUnblocked()) { diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/components/impl/XmlElementStorageTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/components/impl/XmlElementStorageTest.java index bf2eaefa2340..8b3218a49a3b 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/components/impl/XmlElementStorageTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/components/impl/XmlElementStorageTest.java @@ -87,7 +87,7 @@ public class XmlElementStorageTest extends LightPlatformLangTestCase { private Element mySavedElement; public MyXmlElementStorage(Element element, final Disposable parentDisposable) throws StateStorageException { - super("", RoamingType.PER_USER, new MyPathMacroManager(), parentDisposable, "root", null, ComponentVersionProvider.EMPTY); + super("", RoamingType.PER_USER, new MyPathMacroManager(), "root", null, ComponentVersionProvider.EMPTY); myElement = element; } 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 6cef0e8b3a5a..150f173395c8 100644 --- a/platform/projectModel-api/src/com/intellij/openapi/components/StateStorage.java +++ b/platform/projectModel-api/src/com/intellij/openapi/components/StateStorage.java @@ -15,7 +15,6 @@ */ package com.intellij.openapi.components; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileEvent; import com.intellij.util.messages.Topic; @@ -49,7 +48,7 @@ public interface StateStorage { /** * Get changed component names */ - void analyzeExternalChangesAndUpdateIfNeed(@NotNull Collection> changedFiles, @NotNull Set result); + void analyzeExternalChangesAndUpdateIfNeed(@NotNull Collection changedFiles, @NotNull Set result); interface ExternalizationSession { void setState(@NotNull Object component, @NotNull String componentName, @NotNull Object state, @Nullable Storage storageSpec); diff --git a/platform/projectModel-impl/src/com/intellij/openapi/components/impl/stores/DirectoryStorageData.java b/platform/projectModel-impl/src/com/intellij/openapi/components/impl/stores/DirectoryStorageData.java index 8ea0e1ef935f..4a78a79db1d6 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/components/impl/stores/DirectoryStorageData.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/components/impl/stores/DirectoryStorageData.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2014 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. @@ -20,11 +20,10 @@ import com.intellij.openapi.components.StateSplitter; import com.intellij.openapi.components.TrackingPathMacroSubstitutor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.JDOMUtil; -import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.util.text.StringUtilRt; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.PairConsumer; import gnu.trove.THashMap; -import org.jdom.Document; +import gnu.trove.TObjectObjectProcedure; import org.jdom.Element; import org.jdom.JDOMException; import org.jetbrains.annotations.NotNull; @@ -32,33 +31,40 @@ import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; public class DirectoryStorageData { private static final Logger LOG = Logger.getInstance(DirectoryStorageData.class); - private Map> myStates = new THashMap>(); + private Map> myStates = new THashMap>(); private long myLastTimestamp = 0; private DirectoryStorageData myOriginalData; + @NotNull public Set getComponentNames() { return myStates.keySet(); } - public void loadFrom(final @Nullable VirtualFile dir, TrackingPathMacroSubstitutor pathMacroSubstitutor) { + static boolean isStorageFile(@NotNull VirtualFile file) { + // ignore system files like .DS_Store on Mac + return StringUtilRt.endsWithIgnoreCase(file.getNameSequence(), ".xml"); + } + + public void loadFrom(@Nullable VirtualFile dir, @Nullable TrackingPathMacroSubstitutor pathMacroSubstitutor) { if (dir == null || !dir.exists()) { return; } for (VirtualFile file : dir.getChildren()) { - if (!StringUtil.endsWithIgnoreCase(file.getName(), ".xml")) { - //do not load system files like .DS_Store on Mac + if (!isStorageFile(file)) { continue; } try { - final Document document = JDOMUtil.loadDocument(file.contentsToByteArray()); - final Element element = document.getRootElement(); + final Element element = JDOMUtil.loadDocument(file.contentsToByteArray()).detachRootElement(); if (!element.getName().equals(StorageData.COMPONENT)) { LOG.error("Incorrect root tag name (" + element.getName() + ") in " + file.getPresentableUrl()); continue; @@ -72,9 +78,7 @@ public class DirectoryStorageData { if (pathMacroSubstitutor != null) { pathMacroSubstitutor.expandPaths(element); - - final Set unknownMacros = PathMacrosCollector.getMacroNames(element); - pathMacroSubstitutor.addUnknownMacros(componentName, unknownMacros); + pathMacroSubstitutor.addUnknownMacros(componentName, PathMacrosCollector.getMacroNames(element)); } put(componentName, new File(file.getPath()), element, true); @@ -88,10 +92,8 @@ public class DirectoryStorageData { } } - public void put(final String componentName, File file, final Element element, final boolean updateTimestamp) { - LOG.assertTrue(componentName != null, String.format("Component name should not be null for file: %s", file == null ? "NULL!" : file.getPath())); - - Map stateMap = myStates.get(componentName); + public void put(@NotNull String componentName, @NotNull File file, @NotNull Element element, boolean updateTimestamp) { + THashMap stateMap = myStates.get(componentName); if (stateMap == null) { stateMap = new THashMap(); myStates.put(componentName, stateMap); @@ -103,7 +105,7 @@ public class DirectoryStorageData { } } - public void updateLastTimestamp(File file) { + public void updateLastTimestamp(@NotNull File file) { myLastTimestamp = Math.max(myLastTimestamp, file.lastModified()); if (myOriginalData != null) { myOriginalData.myLastTimestamp = myLastTimestamp; @@ -114,42 +116,17 @@ public class DirectoryStorageData { return myLastTimestamp; } - public Map getAllStorageFiles() { - final Map allStorageFiles = new THashMap(); - process(new StorageDataProcessor() { - @Override - public void process(final String componentName, final File file, final Element element) { - allStorageFiles.put(file, file.lastModified()); - } - }); - - return allStorageFiles; - } - - public void processComponent(@NotNull final String componentName, @NotNull final PairConsumer consumer) { - final Map map = myStates.get(componentName); + void processComponent(@NotNull String componentName, @NotNull TObjectObjectProcedure consumer) { + THashMap map = myStates.get(componentName); if (map != null) { - for (File file : map.keySet()) { - consumer.consume(file, map.get(file)); - } - } - } - - public void process(@NotNull final StorageDataProcessor processor) { - for (final String componentName : myStates.keySet()) { - processComponent(componentName, new PairConsumer() { - @Override - public void consume(File file, Element element) { - processor.process(componentName, file, element); - } - }); + map.forEachEntry(consumer); } } @Override protected DirectoryStorageData clone() { final DirectoryStorageData result = new DirectoryStorageData(); - result.myStates = new HashMap>(myStates); + result.myStates = new THashMap>(myStates); result.myLastTimestamp = myLastTimestamp; result.myOriginalData = this; return result; @@ -171,14 +148,15 @@ public class DirectoryStorageData { @Nullable public T getMergedState(String componentName, Class stateClass, StateSplitter splitter, @Nullable T mergeInto) { final List subElements = new ArrayList(); - processComponent(componentName, new PairConsumer() { + processComponent(componentName, new TObjectObjectProcedure() { @Override - public void consume(File file, Element element) { + public boolean execute(File file, Element element) { final List children = element.getChildren(); assert children.size() == 1 : JDOMUtil.writeElement(element, File.separator); - final Element subElement = (Element)children.get(0); + Element subElement = (Element)children.get(0); subElement.detach(); subElements.add(subElement); + return true; } }); @@ -188,8 +166,4 @@ public class DirectoryStorageData { return DefaultStateSerializer.deserializeState(state, stateClass, mergeInto); } - - interface StorageDataProcessor { - void process(String componentName, File file, Element element); - } } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/ProjectLibraryTable.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/ProjectLibraryTable.java index f84441d462a4..e5ddddf4e751 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/ProjectLibraryTable.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/ProjectLibraryTable.java @@ -26,7 +26,6 @@ import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.util.text.UniqueNameGenerator; import org.jdom.Element; -import org.jetbrains.annotations.NonNls; import java.util.ArrayList; import java.util.List; @@ -78,22 +77,16 @@ public class ProjectLibraryTable extends LibraryTableBase { return true; } - public static class LibraryStateSplitter implements StateSplitter { - @Override public List> splitState(Element e) { final UniqueNameGenerator generator = new UniqueNameGenerator(); List> result = new ArrayList>(); - - final List list = e.getChildren(); - for (final Object o : list) { - Element library = (Element)o; - @NonNls final String name = generator.generateUniqueName(FileUtil.sanitizeFileName(library.getAttributeValue(LibraryImpl.LIBRARY_NAME_ATTR))) + ".xml"; + for (Element library : e.getChildren()) { + String name = generator.generateUniqueName(FileUtil.sanitizeFileName(library.getAttributeValue(LibraryImpl.LIBRARY_NAME_ATTR))) + ".xml"; result.add(Pair.create(library, name)); } - return result; } diff --git a/platform/testFramework/src/com/intellij/mock/MockProjectStore.java b/platform/testFramework/src/com/intellij/mock/MockProjectStore.java index eb96333e68bc..2d8c431d5046 100644 --- a/platform/testFramework/src/com/intellij/mock/MockProjectStore.java +++ b/platform/testFramework/src/com/intellij/mock/MockProjectStore.java @@ -23,8 +23,8 @@ import com.intellij.openapi.components.impl.stores.StateStorageManager; import com.intellij.openapi.components.store.ComponentSaveSession; import com.intellij.openapi.project.impl.ProjectImpl; import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.MultiMap; import org.jdom.JDOMException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -148,7 +148,7 @@ public class MockProjectStore implements IProjectStore { @Nullable @Override - public Collection reload(@NotNull Collection> changedFiles) { + public Collection reload(@NotNull MultiMap changedStorages) { return null; }