DirectoryBasedStorage — fix analyzeExternalChangesAndUpdateIfNeed

This commit is contained in:
Vladimir Krivosheev
2014-10-11 20:02:12 +02:00
parent 81164cdcc1
commit 713f9a1c1e
16 changed files with 245 additions and 295 deletions
@@ -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<Pair<VirtualFile, StateStorage>> changedFiles, @NotNull Set<String> result) {
public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Collection<VirtualFile> changedFiles, @NotNull Set<String> result) {
}
public void save() throws StateStorageException {
@@ -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<String> reload(@NotNull Collection<Pair<VirtualFile, StateStorage>> changedFiles) {
public final Collection<String> reload(@NotNull MultiMap<StateStorage, VirtualFile> changedStorages) {
Set<String> componentNames = new SmartHashSet<String>();
for (Pair<VirtualFile, StateStorage> 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<Pair<VirtualFile, StateStorage>> causes, @NotNull IComponentStore.Reloadable store) {
public static ReloadComponentStoreStatus reloadStore(@NotNull Collection<Pair<VirtualFile, StateStorage>> changedStorages, @NotNull IComponentStore.Reloadable store) {
MultiMap<StateStorage, VirtualFile> storageToFiles = MultiMap.createLinkedSet();
for (Pair<VirtualFile, StateStorage> pair : changedStorages) {
storageToFiles.putValue(pair.second, pair.first);
}
Collection<String> 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<VirtualFile, StateStorage> 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<String> notReloadableComponents,
@Nullable Collection<Pair<VirtualFile, StateStorage>> causes) {
@Nullable Collection<Pair<VirtualFile, StateStorage>> 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<VirtualFile, StateStorage> cause : causes) {
StateStorage stateStorage = cause.getSecond();
if (stateStorage instanceof XmlElementStorage) {
((XmlElementStorage)stateStorage).disableSaving();
if (changedStorages != null) {
for (Pair<VirtualFile, StateStorage> cause : changedStorages) {
StateStorage storage = cause.getSecond();
if (storage instanceof StateStorageBase) {
((StateStorageBase)storage).disableSaving();
}
}
}
@@ -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
@@ -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<Pair<VirtualFile, StateStorage>> changedFiles, @NotNull Set<String> result) {
public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Collection<VirtualFile> changedFiles, @NotNull Set<String> result) {
}
}
@@ -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<Pair<VirtualFile, StateStorage>> changedFiles, @NotNull Set<String> result) {
boolean containsSelf = false;
for (Pair<VirtualFile, StateStorage> pair : changedFiles) {
if (pair.second == this && StringUtilRt.endsWithIgnoreCase(pair.first.getNameSequence(), ".xml")) {
containsSelf = true;
break;
}
}
if (!containsSelf) {
return;
}
public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Collection<VirtualFile> changedFiles, @NotNull Set<String> 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<String> currentNames = new SmartHashSet<String>();
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<Element, String> pair : mySplitter.splitState(element)) {
for (Pair<Element, String> 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<String> existingFileNames = new SmartHashSet<String>();
for (String componentName : myStorageData.getComponentNames()) {
myStorageData.processComponent(componentName, new TObjectObjectProcedure<File, Element>() {
@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;
}
}
}
@@ -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);
}
}
}
@@ -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<String> reload(@NotNull Collection<Pair<VirtualFile, StateStorage>> changedFiles);
Collection<String> reload(@NotNull MultiMap<StateStorage, VirtualFile> changedStorages);
}
}
@@ -324,7 +324,6 @@ public class StorageUtil {
}
}
@SuppressWarnings("Contract")
@Nullable
public static Document loadDocument(@Nullable InputStream stream) {
if (stream == null) {
@@ -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<String, Element> newLiveStates) {
Element element = data.save(newLiveStates);
@@ -230,7 +200,7 @@ public abstract class XmlElementStorage implements StateStorage, Disposable {
}
@Override
public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Collection<Pair<VirtualFile, StateStorage>> changedFiles, @NotNull Set<String> result) {
public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Collection<VirtualFile> changedFiles, @NotNull Set<String> 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;
@@ -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;
}
}
@@ -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()) {
@@ -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;
}
@@ -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<Pair<VirtualFile, StateStorage>> changedFiles, @NotNull Set<String> result);
void analyzeExternalChangesAndUpdateIfNeed(@NotNull Collection<VirtualFile> changedFiles, @NotNull Set<String> result);
interface ExternalizationSession {
void setState(@NotNull Object component, @NotNull String componentName, @NotNull Object state, @Nullable Storage storageSpec);
@@ -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<String, Map<File, Element>> myStates = new THashMap<String, Map<File, Element>>();
private Map<String, THashMap<File, Element>> myStates = new THashMap<String, THashMap<File, Element>>();
private long myLastTimestamp = 0;
private DirectoryStorageData myOriginalData;
@NotNull
public Set<String> 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<String> 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<File, Element> stateMap = myStates.get(componentName);
public void put(@NotNull String componentName, @NotNull File file, @NotNull Element element, boolean updateTimestamp) {
THashMap<File, Element> stateMap = myStates.get(componentName);
if (stateMap == null) {
stateMap = new THashMap<File, Element>();
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<File, Long> getAllStorageFiles() {
final Map<File, Long> allStorageFiles = new THashMap<File, Long>();
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<File, Element> consumer) {
final Map<File, Element> map = myStates.get(componentName);
void processComponent(@NotNull String componentName, @NotNull TObjectObjectProcedure<File, Element> consumer) {
THashMap<File, Element> 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<File, Element>() {
@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<String, Map<File, Element>>(myStates);
result.myStates = new THashMap<String, THashMap<File, Element>>(myStates);
result.myLastTimestamp = myLastTimestamp;
result.myOriginalData = this;
return result;
@@ -171,14 +148,15 @@ public class DirectoryStorageData {
@Nullable
public <T> T getMergedState(String componentName, Class<T> stateClass, StateSplitter splitter, @Nullable T mergeInto) {
final List<Element> subElements = new ArrayList<Element>();
processComponent(componentName, new PairConsumer<File, Element>() {
processComponent(componentName, new TObjectObjectProcedure<File, Element>() {
@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);
}
}
@@ -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<Pair<Element, String>> splitState(Element e) {
final UniqueNameGenerator generator = new UniqueNameGenerator();
List<Pair<Element, String>> result = new ArrayList<Pair<Element, String>>();
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;
}
@@ -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<String> reload(@NotNull Collection<Pair<VirtualFile, StateStorage>> changedFiles) {
public Collection<String> reload(@NotNull MultiMap<StateStorage, VirtualFile> changedStorages) {
return null;
}