save: compare using model, but not serialized content

1) Get rid of "get all storages data" just to reload/save/etc — now we are more smart and don't disturb the whole world
2) Sanitize — only storage data are responsible for data "change" state — storage don't hold/maintain own hash
3) Don't use xml prolog to save app files (historically, ROOT_CONFIG also don't use xml prolog). Project files are not affected (backward compatibility).
4) Fix project reloading (especially in case of stream provider) — ensure that new state (from FS) will not be overwritten.
This commit is contained in:
Vladimir Krivosheev
2014-10-01 16:28:09 +02:00
parent eb60abccc3
commit ac5776349e
36 changed files with 1078 additions and 1378 deletions
@@ -19,7 +19,10 @@ import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.JDOMExternalizable;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.NamedJDOMExternalizable;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
@@ -27,7 +30,6 @@ import com.intellij.util.SystemProperties;
import org.jdom.Document;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
import org.jetbrains.jps.model.serialization.JDomSerializationUtil;
import org.junit.Assert;
@@ -37,7 +39,6 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* @author nik
@@ -151,16 +152,5 @@ public class CompilerTestUtil {
@Override
public void save() {
}
@Nullable
@Override
public Set<String> analyzeExternalChanges(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles) {
return null;
}
@Override
public void collectAllStorageFiles(@NotNull List<VirtualFile> files) {
}
}
}
@@ -17,6 +17,7 @@
package com.intellij.openapi.components.impl.stores;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.components.StateStorage;
import com.intellij.openapi.components.StateStorageOperation;
import com.intellij.openapi.components.TrackingPathMacroSubstitutor;
import com.intellij.openapi.module.Module;
@@ -53,4 +54,9 @@ class ModuleStateStorageManager extends StateStorageManagerImpl {
return myModule.getName() + Integer.toHexString(myModule.getModuleFilePath().hashCode());
}
@NotNull
@Override
protected StateStorage.Listener createStorageTopicListener() {
return myModule.getProject().getMessageBus().syncPublisher(StateStorage.PROJECT_STORAGE_TOPIC);
}
}
@@ -29,6 +29,7 @@ import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtil;
import com.intellij.util.PathUtilRt;
import com.intellij.util.messages.MessageBus;
import gnu.trove.THashMap;
import org.jdom.Attribute;
import org.jdom.Element;
@@ -131,8 +132,8 @@ public class ModuleStoreImpl extends BaseFileConfigurableStoreImpl implements IM
@Override
@NotNull
protected Element save() {
Element root = super.save();
protected Element save(@NotNull Map<String, Element> newLiveStates) {
Element root = super.save(newLiveStates);
myOptions.put(VERSION_OPTION, Integer.toString(myVersion));
String[] options = ArrayUtil.toStringArray(myOptions.keySet());
Arrays.sort(options);
@@ -152,11 +153,6 @@ public class ModuleStoreImpl extends BaseFileConfigurableStoreImpl implements IM
return new ModuleFileData(this);
}
@Override
protected int computeHash() {
return super.computeHash() * 31 + myOptions.hashCode();
}
@Nullable
@Override
public Set<String> getChangedComponentNames(@NotNull StorageData newStorageData, @Nullable PathMacroSubstitutor substitutor) {
@@ -168,12 +164,10 @@ public class ModuleStoreImpl extends BaseFileConfigurableStoreImpl implements IM
}
public void setOption(final String optionName, final String optionValue) {
clearHash();
myOptions.put(optionName, optionValue);
}
public void clearOption(final String optionName) {
clearHash();
myOptions.remove(optionName);
}
@@ -245,6 +239,12 @@ public class ModuleStoreImpl extends BaseFileConfigurableStoreImpl implements IM
return ((ProjectEx)myModule.getProject()).isOptimiseTestLoadSpeed();
}
@NotNull
@Override
protected MessageBus getMessageBus() {
return myModule.getMessageBus();
}
@NotNull
@Override
protected StateStorageManager createStateStorageManager() {
@@ -15,8 +15,6 @@
*/
package com.intellij.openapi.components.impl.stores;
import com.intellij.openapi.components.StateStorage;
import com.intellij.openapi.components.StateStorageException;
import com.intellij.openapi.components.TrackingPathMacroSubstitutor;
import com.intellij.openapi.components.store.ComponentSaveSession;
import com.intellij.openapi.module.Module;
@@ -27,12 +25,8 @@ import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.SmartHashSet;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -45,12 +39,17 @@ public class ProjectWithModulesStoreImpl extends ProjectStoreImpl {
}
@Override
public void reinitComponents(@NotNull Set<String> componentNames, boolean reloadData) {
super.reinitComponents(componentNames, reloadData);
protected boolean reinitComponent(@NotNull String componentName, boolean reloadData) {
if (super.reinitComponent(componentName, reloadData)) {
return true;
}
for (Module module : getPersistentModules()) {
((ModuleImpl)module).getStateStore().reinitComponents(componentNames, reloadData);
if (((ModuleStoreImpl)((ModuleImpl)module).getStateStore()).reinitComponent(componentName, reloadData)) {
return true;
}
}
return false;
}
@Override
@@ -87,54 +86,19 @@ public class ProjectWithModulesStoreImpl extends ProjectStoreImpl {
}
@Override
protected SaveSessionImpl createSaveSession() throws StateStorageException {
protected SaveSessionImpl createSaveSession() {
return new ProjectWithModulesSaveSession();
}
private class ProjectWithModulesSaveSession extends ProjectSaveSession {
List<ComponentSaveSession> myModuleSaveSessions = new SmartList<ComponentSaveSession>();
final List<ComponentSaveSession> myModuleSaveSessions = new SmartList<ComponentSaveSession>();
public ProjectWithModulesSaveSession() {
for (Module module : getPersistentModules()) {
myModuleSaveSessions.add(((ModuleImpl)module).getStateStore().startSave());
ContainerUtil.addIfNotNull(myModuleSaveSessions, ((ModuleImpl)module).getStateStore().startSave());
}
}
@Override
public void collectAllStorageFiles(boolean includingSubStructures, @NotNull List<VirtualFile> files) {
super.collectAllStorageFiles(includingSubStructures, files);
if (includingSubStructures) {
for (ComponentSaveSession moduleSaveSession : myModuleSaveSessions) {
moduleSaveSession.collectAllStorageFiles(true, files);
}
}
}
@Override
@Nullable
public Set<String> analyzeExternalChanges(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles) {
Set<String> superResult = super.analyzeExternalChanges(changedFiles);
if (superResult == null) {
return null;
}
Set<String> result = superResult.isEmpty() ? null : new THashSet<String>(superResult);
for (ComponentSaveSession moduleSaveSession : myModuleSaveSessions) {
Set<String> s = moduleSaveSession.analyzeExternalChanges(changedFiles);
if (s == null) {
return null;
}
if (!s.isEmpty()) {
if (result == null) {
result = new SmartHashSet<String>();
}
result.addAll(s);
}
}
return result == null ? Collections.<String>emptySet() : result;
}
@Override
public void finishSave() {
try {
@@ -159,18 +123,6 @@ public class ProjectWithModulesStoreImpl extends ProjectStoreImpl {
}
}
@Override
public void reset() {
try {
for (ComponentSaveSession moduleSaveSession : myModuleSaveSessions) {
moduleSaveSession.reset();
}
}
finally {
super.reset();
}
}
@Override
protected void beforeSave(@NotNull List<Pair<StateStorageManager.SaveSession, VirtualFile>> readonlyFiles) {
super.beforeSave(readonlyFiles);
@@ -17,11 +17,7 @@
package com.intellij.openapi.roots.impl.storage;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PathMacroManager;
import com.intellij.openapi.components.StateStorage;
import com.intellij.openapi.components.StateStorageException;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.components.*;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.options.ConfigurationException;
@@ -42,7 +38,6 @@ import com.intellij.openapi.vfs.VirtualFileAdapter;
import com.intellij.openapi.vfs.VirtualFileEvent;
import com.intellij.openapi.vfs.tracker.VirtualFileTracker;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.messages.MessageBus;
import org.jdom.Element;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
@@ -63,8 +58,6 @@ import java.util.Set;
* Time: 1:42:06 PM
*/
public class ClasspathStorage implements StateStorage {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.storage.ClasspathStorage");
@NonNls public static final String SPECIAL_STORAGE = "special";
public static final String DEFAULT_STORAGE_DESCR = ProjectBundle.message("project.roots.classpath.format.default.descr");
@@ -72,21 +65,17 @@ public class ClasspathStorage implements StateStorage {
@NonNls public static final String CLASSPATH_DIR_OPTION = JpsProjectLoader.CLASSPATH_DIR_ATTRIBUTE;
@NonNls private static final String COMPONENT_TAG = "component";
private Object mySession;
private final ClasspathStorageProvider.ClasspathConverter myConverter;
public ClasspathStorage(Module module) {
myConverter = getProvider(ClassPathStorageUtil.getStorageType(module)).createConverter(module);
final MessageBus messageBus = module.getMessageBus();
final VirtualFileTracker virtualFileTracker =
(VirtualFileTracker)module.getPicoContainer().getComponentInstanceOfType(VirtualFileTracker.class);
final VirtualFileTracker virtualFileTracker = ServiceManager.getService(VirtualFileTracker.class);
if (virtualFileTracker != null) {
final ArrayList<VirtualFile> files = new ArrayList<VirtualFile>();
try {
myConverter.getFileSet().listFiles(files);
for (VirtualFile file : files) {
final Listener listener = messageBus.syncPublisher(STORAGE_TOPIC);
final Listener listener = module.getProject().getMessageBus().syncPublisher(PROJECT_STORAGE_TOPIC);
virtualFileTracker.addTracker(file.getUrl(), new VirtualFileAdapter() {
@Override
public void contentsChanged(@NotNull final VirtualFileEvent event) {
@@ -107,7 +96,7 @@ public class ClasspathStorage implements StateStorage {
@Override
@Nullable
public <T> T getState(final Object component, @NotNull final String componentName, Class<T> stateClass, @Nullable T mergeInto)
public <T> T getState(final Object component, @NotNull final String componentName, @NotNull Class<T> stateClass, @Nullable T mergeInto)
throws StateStorageException {
assert component instanceof ModuleRootManager;
assert componentName.equals("NewModuleRootManager");
@@ -147,8 +136,7 @@ public class ClasspathStorage implements StateStorage {
}
@Override
public boolean hasState(final Object component, @NotNull final String componentName, final Class<?> aClass, final boolean reloadData)
throws StateStorageException {
public boolean hasState(@Nullable final Object component, @NotNull final String componentName, final Class<?> aClass, final boolean reloadData) {
return true;
}
@@ -171,42 +159,23 @@ public class ClasspathStorage implements StateStorage {
@Override
@NotNull
public ExternalizationSession startExternalization() {
final ExternalizationSession session = new ExternalizationSession() {
return new ExternalizationSession() {
@Override
public void setState(@NotNull Object component, @NotNull String componentName, @NotNull Object state, Storage storageSpec) {
assert mySession == this;
ClasspathStorage.this.setState(component, componentName, state);
}
};
}
mySession = session;
return session;
@Nullable
@Override
public SaveSession startSave(@NotNull ExternalizationSession externalizationSession) {
return new MySaveSession();
}
@Override
@NotNull
public SaveSession startSave(@NotNull final ExternalizationSession externalizationSession) {
assert mySession == externalizationSession;
final SaveSession session = new MySaveSession();
mySession = session;
return session;
}
@Override
public void finishSave(@NotNull final SaveSession saveSession) {
try {
LOG.assertTrue(mySession == saveSession);
}
finally {
mySession = null;
}
}
@Override
public void reload(@NotNull Set<String> changedComponents) {
public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles, @NotNull Set<String> result) {
}
public void save() throws StateStorageException {
@@ -402,19 +371,7 @@ public class ClasspathStorage implements StateStorage {
private class MySaveSession implements SaveSession, SafeWriteRequestor {
@Override
public void save() {
assert mySession == this;
ClasspathStorage.this.save();
}
@Override
@Nullable
public Set<String> analyzeExternalChanges(@NotNull final Set<Pair<VirtualFile, StateStorage>> changedFiles) {
return null;
}
@Override
public void collectAllStorageFiles(@NotNull List<VirtualFile> files) {
getFileSet().listFiles(files);
}
}
}
@@ -33,7 +33,7 @@ public class ReloadProjectAction extends AnAction implements DumbAware {
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = CommonDataKeys.PROJECT.getData(e.getDataContext());
if (project != null) {
((ProjectManagerImpl)ProjectManager.getInstance()).reloadProjectImpl(project, false);
((ProjectManagerImpl)ProjectManager.getInstance()).reloadProjectImpl(project, null);
}
}
}
@@ -23,6 +23,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.NamedJDOMExternalizable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.*;
import com.intellij.util.messages.MessageBus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -51,7 +52,7 @@ class ApplicationStoreImpl extends ComponentStoreImpl implements IApplicationSto
private boolean myConfigDirectoryRefreshed;
@Override
protected StorageData createStorageData(String storageSpec) {
protected StorageData createStorageData(@NotNull String storageSpec) {
return new FileBasedStorage.FileStorageData(ROOT_ELEMENT_NAME);
}
@@ -77,6 +78,11 @@ class ApplicationStoreImpl extends ComponentStoreImpl implements IApplicationSto
return super.getMacroSubstitutor(fileSpec);
}
@Override
protected boolean isUseXmlProlog() {
return false;
}
@Override
protected void beforeFileBasedStorageCreate() {
if (!myConfigDirectoryRefreshed && (application.isUnitTestMode() || application.isDispatchThread())) {
@@ -132,8 +138,14 @@ class ApplicationStoreImpl extends ComponentStoreImpl implements IApplicationSto
}
@Override
public Collection<String> reload(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles) throws IOException {
return reload(changedFiles, myApplication.getMessageBus());
public Collection<String> reload(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles) {
return doReload(changedFiles);
}
@Override
@NotNull
protected MessageBus getMessageBus() {
return myApplication.getMessageBus();
}
@NotNull
@@ -25,6 +25,7 @@ import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
abstract class BaseFileConfigurableStoreImpl extends ComponentStoreImpl {
@@ -75,8 +76,8 @@ abstract class BaseFileConfigurableStoreImpl extends ComponentStoreImpl {
@Override
@NotNull
protected Element save() {
Element root = super.save();
protected Element save(@NotNull Map<String, Element> newLiveStates) {
Element root = super.save(newLiveStates);
if (root == null) {
root = new Element(myRootElementName);
}
@@ -90,21 +91,14 @@ abstract class BaseFileConfigurableStoreImpl extends ComponentStoreImpl {
return new BaseStorageData(this);
}
@Override
protected int computeHash() {
int result = super.computeHash();
result = result * 31 + myVersion;
return result;
}
@Nullable
@Override
public Set<String> getChangedComponentNames(@NotNull StorageData storageData, @Nullable PathMacroSubstitutor substitutor) {
BaseStorageData data = (BaseStorageData)storageData;
public Set<String> getChangedComponentNames(@NotNull StorageData newStorageData, @Nullable PathMacroSubstitutor substitutor) {
BaseStorageData data = (BaseStorageData)newStorageData;
if (myVersion != data.myVersion) {
return null;
}
return super.getChangedComponentNames(storageData, substitutor);
return super.getChangedComponentNames(newStorageData, substitutor);
}
}
@@ -15,7 +15,6 @@
*/
package com.intellij.openapi.components.impl.stores;
import com.intellij.diagnostic.IdeErrorsDialog;
import com.intellij.diagnostic.PluginException;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.application.ApplicationManager;
@@ -27,10 +26,14 @@ import com.intellij.openapi.components.store.ReadOnlyModificationException;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.JDOMExternalizable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.RoamingTypeDisabled;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.containers.SmartHashSet;
import com.intellij.util.messages.MessageBus;
import gnu.trove.THashMap;
import org.jdom.Element;
@@ -45,7 +48,6 @@ public abstract class ComponentStoreImpl implements IComponentStore {
private static final Logger LOG = Logger.getInstance(ComponentStoreImpl.class);
private final Map<String, Object> myComponents = Collections.synchronizedMap(new THashMap<String, Object>());
private final List<SettingsSavingComponent> mySettingsSavingComponents = Collections.synchronizedList(new ArrayList<SettingsSavingComponent>());
@Nullable private SaveSessionImpl mySession;
@Nullable
protected abstract StateStorage getDefaultsStorage();
@@ -84,48 +86,42 @@ public abstract class ComponentStoreImpl implements IComponentStore {
}
@Override
public boolean isSaving() {
return mySession != null;
}
@Nullable
public final ComponentSaveSession startSave() {
if (myComponents.isEmpty()) {
return null;
}
StateStorageManager storageManager = getStateStorageManager();
StateStorageManager.ExternalizationSession externalizationSession = storageManager.startExternalization();
String[] names = ArrayUtilRt.toStringArray(myComponents.keySet());
Arrays.sort(names);
for (String name : names) {
Object component = myComponents.get(name);
if (component instanceof PersistentStateComponent) {
commitPersistentComponent((PersistentStateComponent<?>)component, externalizationSession);
}
else if (component instanceof JDOMExternalizable) {
externalizationSession.setStateInOldStorage(component, ComponentManagerImpl.getComponentName(component), component);
}
}
StateStorageManager.SaveSession storageManagerSaveSession = storageManager.startSave(externalizationSession);
if (storageManagerSaveSession == null) {
return null;
}
@Override
@NotNull
public ComponentSaveSession startSave() {
SaveSessionImpl session = createSaveSession();
try {
session.commit();
}
catch (Throwable e) {
try {
session.reset();
}
catch (Exception e1_ignored) {
LOG.info(e1_ignored);
}
PluginId pluginId = IdeErrorsDialog.findPluginId(e);
if (pluginId != null) {
throw new PluginException(e, pluginId);
}
throw new StateStorageException(e);
}
mySession = session;
return mySession;
session.myStorageManagerSaveSession = storageManagerSaveSession;
return session;
}
protected SaveSessionImpl createSaveSession() throws StateStorageException {
protected SaveSessionImpl createSaveSession() {
return new SaveSessionImpl();
}
public void finishSave(@NotNull final ComponentSaveSession saveSession) {
assert mySession == saveSession;
mySession.finishSave();
mySession = null;
}
private <T> void commitPersistentComponent(@NotNull final PersistentStateComponent<T> persistentStateComponent,
private <T> void commitPersistentComponent(@NotNull PersistentStateComponent<T> persistentStateComponent,
@NotNull StateStorageManager.ExternalizationSession session) {
T state = persistentStateComponent.getState();
if (state != null) {
@@ -147,11 +143,14 @@ public abstract class ComponentStoreImpl implements IComponentStore {
loadJdomDefaults(component, componentName);
StateStorage stateStorage = getStateStorageManager().getOldStorage(component, componentName, StateStorageOperation.READ);
if (stateStorage == null) {
return null;
}
if (stateStorage == null) return null;
Element element = getJdomState(component, componentName, stateStorage);
if (element == null) return null;
if (element == null) {
return null;
}
try {
if (LOG.isDebugEnabled()) {
@@ -176,13 +175,17 @@ public abstract class ComponentStoreImpl implements IComponentStore {
myComponents.put(componentName, component);
}
private void loadJdomDefaults(@NotNull final Object component, final String componentName) {
private void loadJdomDefaults(@NotNull Object component, @NotNull String componentName) {
try {
StateStorage defaultsStorage = getDefaultsStorage();
if (defaultsStorage == null) return;
if (defaultsStorage == null) {
return;
}
Element defaultState = getJdomState(component, componentName, defaultsStorage);
if (defaultState == null) return;
if (defaultState == null) {
return;
}
((JDOMExternalizable)component).readExternal(defaultState);
}
@@ -192,8 +195,7 @@ public abstract class ComponentStoreImpl implements IComponentStore {
}
@Nullable
private static Element getJdomState(final Object component, final String componentName, @NotNull final StateStorage defaultsStorage)
throws StateStorageException {
private static Element getJdomState(final Object component, @NotNull String componentName, @NotNull StateStorage defaultsStorage) {
ComponentRoamingManager roamingManager = ComponentRoamingManager.getInstance();
if (component instanceof RoamingTypeDisabled) {
roamingManager.setRoamingType(componentName, RoamingType.DISABLED);
@@ -351,7 +353,6 @@ public abstract class ComponentStoreImpl implements IComponentStore {
protected StateStorageManager.SaveSession myStorageManagerSaveSession;
public SaveSessionImpl() {
ShutDownTracker.getInstance().registerStopperThread(Thread.currentThread());
}
@NotNull
@@ -374,55 +375,8 @@ public abstract class ComponentStoreImpl implements IComponentStore {
@Override
public void finishSave() {
try {
getStateStorageManager().finishSave(myStorageManagerSaveSession);
myStorageManagerSaveSession = null;
}
finally {
ShutDownTracker.getInstance().unregisterStopperThread(Thread.currentThread());
mySession = null;
}
}
@Override
public void reset() {
try {
getStateStorageManager().reset();
myStorageManagerSaveSession = null;
}
finally {
ShutDownTracker.getInstance().unregisterStopperThread(Thread.currentThread());
mySession = null;
}
}
protected void commit() {
final StateStorageManager storageManager = getStateStorageManager();
final StateStorageManager.ExternalizationSession session = storageManager.startExternalization();
String[] names = ArrayUtil.toStringArray(myComponents.keySet());
Arrays.sort(names);
for (String name : names) {
Object component = myComponents.get(name);
if (component instanceof PersistentStateComponent) {
commitPersistentComponent((PersistentStateComponent<?>)component, session);
}
else if (component instanceof JDOMExternalizable) {
session.setStateInOldStorage(component, ComponentManagerImpl.getComponentName(component), component);
}
}
myStorageManagerSaveSession = storageManager.startSave(session);
}
@Override
@Nullable
public Set<String> analyzeExternalChanges(@NotNull final Set<Pair<VirtualFile, StateStorage>> changedFiles) {
return myStorageManagerSaveSession.analyzeExternalChanges(changedFiles);
}
@Override
public void collectAllStorageFiles(boolean includingSubStructures, @NotNull List<VirtualFile> files) {
myStorageManagerSaveSession.collectAllStorageFiles(files);
getStateStorageManager().finishSave(myStorageManagerSaveSession);
myStorageManagerSaveSession = null;
}
}
@@ -455,64 +409,61 @@ public abstract class ComponentStoreImpl implements IComponentStore {
}
@Override
public void reinitComponents(@NotNull final Set<String> componentNames, final boolean reloadData) {
for (String componentName : componentNames) {
final PersistentStateComponent component = (PersistentStateComponent)myComponents.get(componentName);
if (component != null) {
initPersistentComponent(component, reloadData);
}
public final void reinitComponents(@NotNull Set<String> componentNames, boolean reloadData) {
reinitComponents(componentNames, Collections.<String>emptySet(), reloadData);
}
protected boolean reinitComponent(@NotNull String componentName, boolean reloadData) {
PersistentStateComponent component = (PersistentStateComponent)myComponents.get(componentName);
if (component == null) {
return false;
}
else {
initPersistentComponent(component, reloadData);
return true;
}
}
protected void doReload(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles, @NotNull Set<String> componentNames) {
for (Pair<VirtualFile, StateStorage> pair : changedFiles) {
assert pair != null;
StateStorage storage = pair.second;
assert storage != null : "Null storage for: " + pair.first;
storage.reload(componentNames);
}
}
@NotNull
protected abstract MessageBus getMessageBus();
@Nullable
protected final Collection<String> reload(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles, @NotNull MessageBus messageBus) {
ComponentSaveSession saveSession = startSave();
Set<String> componentNames;
protected final Collection<String> doReload(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles) {
Set<String> componentNames = new SmartHashSet<String>();
for (Pair<VirtualFile, StateStorage> pair : changedFiles) {
StateStorage storage = pair.second;
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);
}
catch (Throwable e) {
LOG.error(e);
}
}
if (componentNames.isEmpty()) {
return Collections.emptySet();
}
Collection<String> notReloadableComponents = getNotReloadableComponents(componentNames);
reinitComponents(componentNames, notReloadableComponents, false);
return notReloadableComponents.isEmpty() ? null : notReloadableComponents;
}
@Override
public final void reinitComponents(@NotNull Set<String> componentNames, @NotNull Collection<String> notReloadableComponents, boolean reloadData) {
MessageBus messageBus = getMessageBus();
messageBus.syncPublisher(BatchUpdateListener.TOPIC).onBatchUpdateStarted();
try {
componentNames = saveSession.analyzeExternalChanges(changedFiles);
if (componentNames == null) {
return Collections.emptyList();
}
for (Pair<VirtualFile, StateStorage> pair : changedFiles) {
if (pair.second == null) {
return Collections.emptyList();
for (String componentName : componentNames) {
if (!notReloadableComponents.contains(componentName)) {
reinitComponent(componentName, reloadData);
}
}
Collection<String> currentNotReloadableComponents = getNotReloadableComponents(componentNames);
StorageUtil.logStateDiffInfo(changedFiles, componentNames);
if (!currentNotReloadableComponents.isEmpty()) {
return currentNotReloadableComponents;
}
}
finally {
finishSave(saveSession);
messageBus.syncPublisher(BatchUpdateListener.TOPIC).onBatchUpdateFinished();
}
if (!componentNames.isEmpty()) {
messageBus.syncPublisher(BatchUpdateListener.TOPIC).onBatchUpdateStarted();
try {
doReload(changedFiles, componentNames);
reinitComponents(componentNames, false);
}
finally {
messageBus.syncPublisher(BatchUpdateListener.TOPIC).onBatchUpdateFinished();
}
}
return null;
}
}
@@ -20,8 +20,6 @@ import com.intellij.openapi.options.StreamProvider;
import com.intellij.openapi.project.impl.ProjectImpl;
import com.intellij.openapi.project.impl.ProjectManagerImpl;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -30,8 +28,6 @@ import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
//todo: extends from base store class
public class DefaultProjectStoreImpl extends ProjectStoreImpl {
@@ -77,8 +73,21 @@ public class DefaultProjectStoreImpl extends ProjectStoreImpl {
}
@Override
protected MySaveSession createSaveSession(final MyExternalizationSession externalizationSession) {
return new DefaultSaveSession(externalizationSession);
protected MySaveSession createSaveSession(@NotNull StorageData storageData) {
return new MySaveSession(storageData) {
@Override
protected void doSave(@Nullable Element element) {
myProjectManager.setDefaultProjectRootElement(element == null ? null : element);
}
// we must not collapse paths here, because our solution is just a big hack
// by default, getElementToSave() returns collapsed paths -> setDefaultProjectRootElement -> project manager writeExternal -> save -> compare old and new - diff because old has expanded, but new collapsed
// -> needless save
@Override
protected boolean isCollapsePathsOnSave() {
return false;
}
};
}
@Override
@@ -86,22 +95,6 @@ public class DefaultProjectStoreImpl extends ProjectStoreImpl {
protected StorageData createStorageData() {
return new BaseStorageData(ROOT_TAG_NAME);
}
class DefaultSaveSession extends MySaveSession {
public DefaultSaveSession(MyExternalizationSession externalizationSession) {
super(externalizationSession);
}
@Override
protected void doSave() throws StateStorageException {
Element element = getElementToSave();
myProjectManager.setDefaultProjectRootElement(element == null ? null : element);
}
@Override
public void collectAllStorageFiles(@NotNull List<VirtualFile> files) {
}
}
};
//noinspection deprecation
@@ -151,15 +144,15 @@ public class DefaultProjectStoreImpl extends ProjectStoreImpl {
return new MyExternalizationSession(storage);
}
@NotNull
@Nullable
@Override
public SaveSession startSave(@NotNull ExternalizationSession externalizationSession) {
return new MySaveSession(storage, externalizationSession);
StateStorage.SaveSession saveSession = storage.startSave(((MyExternalizationSession)externalizationSession).externalizationSession);
return saveSession == null ? null : new MySaveSession(saveSession);
}
@Override
public void finishSave(@NotNull SaveSession saveSession) {
storage.finishSave(((MySaveSession)saveSession).saveSession);
}
@NotNull
@@ -201,10 +194,6 @@ public class DefaultProjectStoreImpl extends ProjectStoreImpl {
public Collection<String> getStorageFileNames() {
throw new UnsupportedOperationException("Method getStorageFileNames not implemented in " + getClass());
}
@Override
public void reset() {
}
};
}
@@ -236,23 +225,12 @@ public class DefaultProjectStoreImpl extends ProjectStoreImpl {
private static class MySaveSession implements StateStorageManager.SaveSession {
@NotNull private final StateStorage.SaveSession saveSession;
public MySaveSession(@NotNull XmlElementStorage storage, @NotNull StateStorageManager.ExternalizationSession externalizationSession) {
saveSession = storage.startSave(((MyExternalizationSession)externalizationSession).externalizationSession);
}
//returns set of component which were changed, null if changes are much more than just component state.
@Override
@Nullable
public Set<String> analyzeExternalChanges(@NotNull Set<Pair<VirtualFile, StateStorage>> files) {
throw new UnsupportedOperationException("Method analyzeExternalChanges not implemented in " + getClass());
public MySaveSession(@NotNull StateStorage.SaveSession saveSession) {
this.saveSession = saveSession;
}
@Override
public void collectAllStorageFiles(@NotNull List<VirtualFile> files) {
}
@Override
public void save() throws StateStorageException {
public void save() {
saveSession.save();
}
}
@@ -20,6 +20,8 @@ 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;
import org.jdom.Element;
@@ -43,7 +45,9 @@ class DefaultsStateStorage implements StateStorage {
@Nullable
public Element getState(final Object component, final String componentName) throws StateStorageException {
final URL url = DecodeDefaultsUtil.getDefaults(component, componentName);
if (url == null) return null;
if (url == null) {
return null;
}
try {
Document document = JDOMUtil.loadDocument(url);
@@ -64,33 +68,29 @@ class DefaultsStateStorage implements StateStorage {
}
}
@Override
@Nullable
public <T> T getState(final Object component, @NotNull final String componentName, final Class<T> stateClass, @Nullable final T mergeInto) throws
StateStorageException {
public <T> T getState(final Object component, @NotNull final String componentName, @NotNull final Class<T> stateClass, @Nullable final T mergeInto) {
return DefaultStateSerializer.deserializeState(getState(component, componentName), stateClass, mergeInto);
}
public boolean hasState(final Object component, @NotNull final String componentName, final Class<?> aClass, final boolean reloadData) throws StateStorageException {
final URL url = DecodeDefaultsUtil.getDefaults(component, componentName);
return url != null;
@Override
public boolean hasState(@Nullable final Object component, @NotNull final String componentName, final Class<?> aClass, final boolean reloadData) {
return DecodeDefaultsUtil.getDefaults(component, componentName) != null;
}
@Override
@NotNull
public ExternalizationSession startExternalization() {
throw new UnsupportedOperationException("Method startExternalization not implemented in " + getClass());
}
@NotNull
@Override
public SaveSession startSave(@NotNull ExternalizationSession externalizationSession) {
throw new UnsupportedOperationException("Method startSave not implemented in " + getClass());
return null;
}
public void finishSave(@NotNull SaveSession saveSession) {
throw new UnsupportedOperationException("Method finishSave not implemented in " + getClass());
@Override
public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles, @NotNull Set<String> result) {
}
public void reload(@NotNull Set<String> changedComponents) {
throw new UnsupportedOperationException("Method reload not implemented in " + getClass());
}
}
@@ -30,19 +30,13 @@ 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.ContainerUtil;
import com.intellij.util.containers.SmartHashSet;
import com.intellij.util.messages.MessageBus;
import gnu.trove.THashSet;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.picocontainer.PicoContainer;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Set;
//todo: support missing plugins
@@ -55,42 +49,41 @@ public class DirectoryBasedStorage implements StateStorage, Disposable {
private final StateSplitter mySplitter;
private final FileTypeManager myFileTypeManager;
private Object mySession;
private DirectoryStorageData myStorageData = null;
public DirectoryBasedStorage(@Nullable TrackingPathMacroSubstitutor pathMacroSubstitutor,
@NotNull String dir,
@NotNull StateSplitter splitter,
@NotNull Disposable parentDisposable,
@NotNull PicoContainer picoContainer) {
@Nullable final Listener listener) {
myPathMacroSubstitutor = pathMacroSubstitutor;
myDir = new File(dir);
mySplitter = splitter;
Disposer.register(parentDisposable, this);
VirtualFileTracker virtualFileTracker = (VirtualFileTracker)picoContainer.getComponentInstanceOfType(VirtualFileTracker.class);
MessageBus messageBus = (MessageBus)picoContainer.getComponentInstanceOfType(MessageBus.class);
if (virtualFileTracker != null && messageBus != null) {
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, '/');
final Listener listener = messageBus.syncPublisher(STORAGE_TOPIC);
virtualFileTracker.addTracker(fileUrl, new VirtualFileAdapter() {
@Override
public void contentsChanged(@NotNull final VirtualFileEvent event) {
if (!StringUtil.endsWithIgnoreCase(event.getFile().getName(), ".xml")) return;
if (!StringUtilRt.endsWithIgnoreCase(event.getFile().getNameSequence(), ".xml")) return;
assert listener != null;
listener.storageFileChanged(event, DirectoryBasedStorage.this);
}
@Override
public void fileDeleted(@NotNull final VirtualFileEvent event) {
if (!StringUtil.endsWithIgnoreCase(event.getFile().getName(), ".xml")) return;
if (!StringUtilRt.endsWithIgnoreCase(event.getFile().getNameSequence(), ".xml")) return;
assert listener != null;
listener.storageFileChanged(event, DirectoryBasedStorage.this);
}
@Override
public void fileCreated(@NotNull final VirtualFileEvent event) {
if (!StringUtil.endsWithIgnoreCase(event.getFile().getName(), ".xml")) return;
if (!StringUtilRt.endsWithIgnoreCase(event.getFile().getNameSequence(), ".xml")) return;
assert listener != null;
listener.storageFileChanged(event, DirectoryBasedStorage.this);
}
}, false, this);
@@ -100,11 +93,30 @@ public class DirectoryBasedStorage implements StateStorage, Disposable {
}
@Override
@Nullable
public <T> T getState(final Object component, @NotNull final String componentName, Class<T> stateClass, @Nullable T mergeInto)
throws StateStorageException {
if (myStorageData == null) myStorageData = loadState();
public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Set<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;
}
// todo reload only changed file, compute diff
myStorageData = loadState();
result.addAll(myStorageData.getComponentNames());
}
@Override
@Nullable
public <T> T getState(final Object component, @NotNull final String componentName, @NotNull Class<T> stateClass, @Nullable T mergeInto) {
if (myStorageData == null) {
myStorageData = loadState();
}
if (!myStorageData.containsComponent(componentName)) {
return DefaultStateSerializer.deserializeState(new Element(StorageData.COMPONENT), stateClass, mergeInto);
@@ -119,11 +131,14 @@ public class DirectoryBasedStorage implements StateStorage, Disposable {
return storageData;
}
@Override
public boolean hasState(final Object component, @NotNull String componentName, final Class<?> aClass, final boolean reloadData) throws StateStorageException {
if (!myDir.exists()) return false;
if (reloadData) myStorageData = null;
public boolean hasState(@Nullable final Object component, @NotNull String componentName, final Class<?> aClass, final boolean reloadData) {
if (!myDir.exists()) {
return false;
}
if (reloadData) {
myStorageData = null;
}
return true;
}
@@ -138,35 +153,13 @@ public class DirectoryBasedStorage implements StateStorage, Disposable {
LOG.error(e);
}
}
final ExternalizationSession session = new MyExternalizationSession(myStorageData.clone());
mySession = session;
return session;
return new MyExternalizationSession(myStorageData.clone());
}
@Nullable
@Override
@NotNull
public SaveSession startSave(@NotNull final ExternalizationSession externalizationSession) {
assert mySession == externalizationSession;
final MySaveSession session =
new MySaveSession(((MyExternalizationSession)externalizationSession).myStorageData, myPathMacroSubstitutor);
mySession = session;
return session;
}
@Override
public void finishSave(@NotNull final SaveSession saveSession) {
try {
LOG.assertTrue(mySession == saveSession);
} finally {
mySession = null;
}
}
@Override
public void reload(@NotNull Set<String> changedComponents) {
myStorageData = loadState();
public SaveSession startSave(@NotNull ExternalizationSession externalizationSession) {
return new MySaveSession(((MyExternalizationSession)externalizationSession).myStorageData, myPathMacroSubstitutor);
}
@Override
@@ -177,14 +170,13 @@ public class DirectoryBasedStorage implements StateStorage, Disposable {
private final DirectoryStorageData myStorageData;
private final TrackingPathMacroSubstitutor myPathMacroSubstitutor;
private MySaveSession(final DirectoryStorageData storageData, final TrackingPathMacroSubstitutor pathMacroSubstitutor) {
private MySaveSession(@NotNull DirectoryStorageData storageData, @Nullable TrackingPathMacroSubstitutor pathMacroSubstitutor) {
myStorageData = storageData;
myPathMacroSubstitutor = pathMacroSubstitutor;
}
@Override
public void save() throws StateStorageException {
assert mySession == this;
final Set<String> currentNames = new SmartHashSet<String>();
File[] children = myDir.listFiles();
if (children != null) {
@@ -206,7 +198,7 @@ public class DirectoryBasedStorage implements StateStorage, Disposable {
}
if (file.lastModified() <= myStorageData.getLastTimeStamp()) {
StorageUtil.save(file, element, MySaveSession.this, false, null);
StorageUtil.save(file, element, MySaveSession.this, false, LocalFileSystem.getInstance().findFileByIoFile(file));
myStorageData.updateLastTimestamp(file);
}
}
@@ -243,39 +235,6 @@ public class DirectoryBasedStorage implements StateStorage, Disposable {
myStorageData.clear();
}
@Override
@Nullable
public Set<String> analyzeExternalChanges(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles) {
boolean containsSelf = false;
for (Pair<VirtualFile, StateStorage> pair : changedFiles) {
if (pair.second == DirectoryBasedStorage.this) {
if (StringUtilRt.endsWithIgnoreCase(pair.first.getNameSequence(), ".xml")) {
containsSelf = true;
break;
}
}
}
if (!containsSelf) {
return Collections.emptySet();
}
if (myStorageData.getComponentNames().isEmpty()) {
// no state yet, so try to initialize it now
return new THashSet<String>(loadState().getComponentNames());
}
else {
return new THashSet<String>(myStorageData.getComponentNames());
}
}
@Override
public void collectAllStorageFiles(@NotNull List<VirtualFile> files) {
for (File file : myStorageData.getAllStorageFiles().keySet()) {
ContainerUtil.addIfNotNull(files, LocalFileSystem.getInstance().findFileByIoFile(file));
}
}
}
private class MyExternalizationSession implements ExternalizationSession {
@@ -287,29 +246,30 @@ public class DirectoryBasedStorage implements StateStorage, Disposable {
@Override
public void setState(@NotNull Object component, @NotNull String componentName, @NotNull Object state, Storage storageSpec) {
assert mySession == this;
setState(componentName, state, storageSpec);
}
private void setState(final String componentName, @NotNull Object state, final Storage storageSpec) {
Element element;
try {
final Element element = DefaultStateSerializer.serializeState(state, storageSpec);
if (element != null) {
for (Pair<Element, String> pair : mySplitter.splitState(element)) {
Element e = pair.first;
String name = pair.second;
Element statePart = new Element(StorageData.COMPONENT);
statePart.setAttribute(StorageData.NAME, componentName);
statePart.addContent(e.detach());
myStorageData.put(componentName, new File(myDir, name), statePart, false);
}
}
element = DefaultStateSerializer.serializeState(state, storageSpec);
}
catch (WriteExternalException e) {
throw new StateStorageException(e);
}
catch (Throwable e) {
LOG.info("Unable to serialize component state!", e);
return;
}
if (element != null) {
for (Pair<Element, String> pair : mySplitter.splitState(element)) {
Element e = pair.first;
String name = pair.second;
Element statePart = new Element(StorageData.COMPONENT);
statePart.setAttribute(StorageData.NAME, componentName);
statePart.addContent(e.detach());
myStorageData.put(componentName, new File(myDir, name), statePart, false);
}
}
}
}
}
@@ -22,23 +22,24 @@ import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.tracker.VirtualFileTracker;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.messages.MessageBus;
import com.intellij.util.LineSeparator;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.picocontainer.PicoContainer;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.Collections;
import java.util.Set;
public class FileBasedStorage extends XmlElementStorage {
@@ -47,6 +48,7 @@ public class FileBasedStorage extends XmlElementStorage {
private final String myFilePath;
private final File myFile;
private volatile VirtualFile myCachedVirtualFile;
private LineSeparator myLineSeparator;
public FileBasedStorage(@NotNull String filePath,
@NotNull String fileSpec,
@@ -54,7 +56,7 @@ public class FileBasedStorage extends XmlElementStorage {
@Nullable TrackingPathMacroSubstitutor pathMacroManager,
@NotNull String rootElementName,
@NotNull Disposable parentDisposable,
PicoContainer picoContainer,
@Nullable final Listener listener,
@Nullable StreamProvider streamProvider,
ComponentVersionProvider componentVersionProvider) {
super(fileSpec, roamingType, pathMacroManager, parentDisposable, rootElementName, streamProvider, componentVersionProvider);
@@ -62,81 +64,88 @@ public class FileBasedStorage extends XmlElementStorage {
myFilePath = filePath;
myFile = new File(filePath);
VirtualFileTracker virtualFileTracker = ServiceManager.getService(VirtualFileTracker.class);
MessageBus messageBus = (MessageBus)picoContainer.getComponentInstanceOfType(MessageBus.class);
if (virtualFileTracker != null && messageBus != null) {
final Listener listener = messageBus.syncPublisher(STORAGE_TOPIC);
virtualFileTracker.addTracker(LocalFileSystem.PROTOCOL_PREFIX + myFile.getAbsolutePath().replace(File.separatorChar, '/'), new VirtualFileAdapter() {
@Override
public void fileMoved(@NotNull VirtualFileMoveEvent event) {
myCachedVirtualFile = null;
}
@Override
public void fileDeleted(@NotNull VirtualFileEvent event) {
myCachedVirtualFile = null;
}
@Override
public void fileCreated(@NotNull VirtualFileEvent event) {
myCachedVirtualFile = event.getFile();
}
@Override
public void contentsChanged(@NotNull final VirtualFileEvent event) {
if (!isDisposed()) {
listener.storageFileChanged(event, FileBasedStorage.this);
if (listener != null) {
VirtualFileTracker virtualFileTracker = ServiceManager.getService(VirtualFileTracker.class);
if (virtualFileTracker != null) {
virtualFileTracker.addTracker(LocalFileSystem.PROTOCOL_PREFIX + myFile.getAbsolutePath().replace(File.separatorChar, '/'), new VirtualFileAdapter() {
@Override
public void fileMoved(@NotNull VirtualFileMoveEvent event) {
myCachedVirtualFile = null;
}
}
}, false, this);
@Override
public void fileDeleted(@NotNull VirtualFileEvent event) {
myCachedVirtualFile = null;
}
@Override
public void fileCreated(@NotNull VirtualFileEvent event) {
myCachedVirtualFile = event.getFile();
}
@Override
public void contentsChanged(@NotNull final VirtualFileEvent event) {
if (!isDisposed()) {
assert listener != null;
listener.storageFileChanged(event, FileBasedStorage.this);
}
}
}, false, this);
}
}
}
protected boolean isUseXmlProlog() {
return false;
}
protected boolean isUseLfLineSeparatorByDefault() {
return isUseXmlProlog();
}
@Override
protected MySaveSession createSaveSession(final MyExternalizationSession externalizationSession) {
return new FileSaveSession(externalizationSession);
protected MySaveSession createSaveSession(@NotNull StorageData storageData) {
return new FileSaveSession(storageData);
}
private class FileSaveSession extends MySaveSession {
protected FileSaveSession(MyExternalizationSession externalizationSession) {
super(externalizationSession);
protected FileSaveSession(@NotNull StorageData storageData) {
super(storageData);
}
@Override
protected boolean physicalContentNeedsSave() {
VirtualFile file = getVirtualFile();
if (file == null || !file.exists()) {
return !myStorageData.isEmpty();
protected void doSave(@Nullable Element element) throws IOException {
if (myLineSeparator == null) {
myLineSeparator = isUseLfLineSeparatorByDefault() ? LineSeparator.LF : LineSeparator.getSystemLineSeparator();
}
Element element = getElementToSave();
return element == null || !StorageUtil.contentEquals(element, file);
}
@Override
protected int calcHash() {
int hash = myStorageData.getHash();
if (myPathMacroSubstitutor != null) {
hash = 31 * hash + myPathMacroSubstitutor.hashCode();
}
return hash;
}
@Override
protected void doSave() throws StateStorageException {
if (myBlockSavingTheContent) {
return;
}
if (ApplicationManager.getApplication().isUnitTestMode() && myFile != null && StringUtil.startsWithChar(myFile.getPath(), '$')) {
BufferExposingByteArrayOutputStream content = element == null ? null : StorageUtil.writeToBytes(element, myLineSeparator.getSeparatorString());
if (ApplicationManager.getApplication().isUnitTestMode() && StringUtil.startsWithChar(myFile.getPath(), '$')) {
throw new StateStorageException("It seems like some macros were not expanded for path: " + myFile.getPath());
}
LOG.assertTrue(myFile != null);
myCachedVirtualFile = StorageUtil.save(myFile, getElementToSave(), this, true, myCachedVirtualFile);
}
try {
if (myStreamProvider != null && myStreamProvider.isEnabled()) {
// stream provider always use LF separator
saveForProvider(myLineSeparator == LineSeparator.LF ? content : null, element);
}
}
catch (Throwable e) {
LOG.error(e);
}
@Override
public void collectAllStorageFiles(@NotNull List<VirtualFile> files) {
ContainerUtil.addIfNotNull(files, getVirtualFile());
if (content == null) {
StorageUtil.deleteFile(myFile, this, getVirtualFile());
myCachedVirtualFile = null;
}
else {
VirtualFile file = getVirtualFile();
if (file == null || !file.exists()) {
FileUtil.createParentDirs(myFile);
file = null;
}
myCachedVirtualFile = StorageUtil.writeFile(myFile, this, file, content, isUseXmlProlog() ? myLineSeparator : null);
}
}
}
@@ -205,28 +214,31 @@ public class FileBasedStorage extends XmlElementStorage {
if (file.getLength() == 0) {
return processReadException(null);
}
return StorageData.load(file);
CharBuffer charBuffer = CharsetToolkit.UTF8_CHARSET.decode(ByteBuffer.wrap(file.contentsToByteArray()));
myLineSeparator = StorageUtil.detectLineSeparators(charBuffer, isUseLfLineSeparatorByDefault() ? null : LineSeparator.LF);
return JDOMUtil.loadDocument(charBuffer).getRootElement();
}
catch (final JDOMException e) {
catch (JDOMException e) {
return processReadException(e);
}
catch (final IOException e) {
catch (IOException e) {
return processReadException(e);
}
}
@Nullable
private Element processReadException(@Nullable final Exception e) {
private Element processReadException(@Nullable Exception e) {
boolean contentTruncated = e == null;
myBlockSavingTheContent = isProjectOrModuleOrWorkspaceFile() && !contentTruncated;
if (!ApplicationManager.getApplication().isUnitTestMode() && !ApplicationManager.getApplication().isHeadlessEnvironment()) {
if (e != null) {
LOG.info(e);
}
final String message = "Cannot load settings from file '" + myFile.getPath() + "': " + (e == null ? "content truncated" : e.getLocalizedMessage()) + "\n" +
getInvalidContentMessage(contentTruncated);
Notifications.Bus.notify(
new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, "Load Settings", message, NotificationType.WARNING));
new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, "Load Settings",
"Cannot load settings from file '" + myFile.getPath() + "': " + (e == null ? "content truncated" : e.getLocalizedMessage()) + "\n" +
getInvalidContentMessage(contentTruncated), NotificationType.WARNING));
}
return null;
@@ -255,26 +267,26 @@ public class FileBasedStorage extends XmlElementStorage {
StorageUtil.deleteFile(myFile, this, myCachedVirtualFile);
// if data was loaded, mark as changed all loaded components
if (myLoadedData != null) {
changedComponentNames.addAll(myLoadedData.myComponentStates.keySet());
resetData();
changedComponentNames.addAll(myLoadedData.getComponentNames());
myLoadedData = null;
}
}
else if (myLoadedData != null) {
StorageData newStorageData = createStorageData();
loadState(newStorageData, newElement);
changedComponentNames.addAll(newStorageData.getChangedComponentNames(myLoadedData, myPathMacroSubstitutor));
changedComponentNames.addAll(myLoadedData.getChangedComponentNames(newStorageData, myPathMacroSubstitutor));
myLoadedData = newStorageData;
}
}
catch (Exception e) {
LOG.warn(e);
catch (Throwable e) {
LOG.error(e);
}
}
@Nullable
@Deprecated
public File updateFileExternallyFromStreamProviders() throws IOException {
Element element = getElement(loadData(true));
Element element = getElement(loadData(true), true, Collections.<String, Element>emptyMap());
if (element == null) {
FileUtil.delete(myFile);
return null;
@@ -21,7 +21,6 @@ import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.Collection;
import java.util.Set;
@@ -39,5 +38,5 @@ public interface IApplicationStore extends IComponentStore {
* list of not reloadable components (reload is not performed)
*/
@Nullable
Collection<String> reload(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles) throws IOException;
Collection<String> reload(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles);
}
@@ -18,6 +18,7 @@ package com.intellij.openapi.components.impl.stores;
import com.intellij.openapi.components.StateStorageException;
import com.intellij.openapi.components.store.ComponentSaveSession;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.Collection;
@@ -28,6 +29,8 @@ public interface IComponentStore {
void reinitComponents(@NotNull Set<String> componentNames, boolean reloadData);
void reinitComponents(@NotNull Set<String> componentNames, @NotNull Collection<String> notReloadableComponents, boolean reloadData);
@NotNull
Collection<String> getNotReloadableComponents(@NotNull Collection<String> componentNames);
@@ -35,8 +38,6 @@ public interface IComponentStore {
void load() throws IOException, StateStorageException;
boolean isSaving();
@NotNull
StateStorageManager getStateStorageManager();
@@ -49,6 +50,6 @@ public interface IComponentStore {
}
}
@NotNull
@Nullable
ComponentSaveSession startSave();
}
@@ -55,7 +55,7 @@ public interface IProjectStore extends IComponentStore {
@Nullable
String getPresentableUrl();
boolean reload(@NotNull Set<Pair<VirtualFile,StateStorage>> changedFiles) throws IOException;
boolean reload(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles);
//------ This methods should be got rid of
/** @deprecated to remove in IDEA 14 */
@@ -72,4 +72,10 @@ class ProjectStateStorageManager extends StateStorageManagerImpl {
private static boolean isWorkspace(final Map options) {
return options != null && Boolean.parseBoolean((String)options.get(ProjectStoreImpl.OPTION_WORKSPACE));
}
@NotNull
@Override
protected StateStorage.Listener createStorageTopicListener() {
return myProject.getMessageBus().syncPublisher(StateStorage.PROJECT_STORAGE_TOPIC);
}
}
@@ -39,6 +39,7 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.util.PathUtilRt;
import com.intellij.util.containers.OrderedSet;
import com.intellij.util.messages.MessageBus;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NonNls;
@@ -460,7 +461,7 @@ class ProjectStoreImpl extends BaseFileConfigurableStoreImpl implements IProject
}
@Override
protected SaveSessionImpl createSaveSession() throws StateStorageException {
protected SaveSessionImpl createSaveSession() {
return new ProjectSaveSession();
}
@@ -630,7 +631,13 @@ class ProjectStoreImpl extends BaseFileConfigurableStoreImpl implements IProject
}
@Override
public boolean reload(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles) throws IOException {
return reload(changedFiles, myProject.getMessageBus()) == null;
public boolean reload(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles) {
return doReload(changedFiles) == null;
}
@NotNull
@Override
protected MessageBus getMessageBus() {
return myProject.getMessageBus();
}
}
@@ -18,14 +18,10 @@ package com.intellij.openapi.components.impl.stores;
import com.intellij.openapi.components.*;
import com.intellij.openapi.options.StreamProvider;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* @author mike
@@ -62,7 +58,10 @@ public interface StateStorageManager {
@NotNull
ExternalizationSession startExternalization();
@NotNull
/**
* return null if nothing to save
*/
@Nullable
SaveSession startSave(@NotNull ExternalizationSession externalizationSession);
void finishSave(@NotNull SaveSession saveSession);
@@ -84,8 +83,6 @@ public interface StateStorageManager {
@Nullable
com.intellij.openapi.components.impl.stores.StreamProvider getStreamProvider();
void reset();
interface ExternalizationSession {
void setState(@NotNull Storage[] storageSpecs, @NotNull Object component, @NotNull String componentName, @NotNull Object state);
@@ -93,12 +90,6 @@ public interface StateStorageManager {
}
interface SaveSession {
// returns set of component which were changed, null if changes are much more than just component state
@Nullable
Set<String> analyzeExternalChanges(@NotNull Set<Pair<VirtualFile, StateStorage>> files);
void collectAllStorageFiles(@NotNull List<VirtualFile> files);
void save() throws StateStorageException;
void save();
}
}
@@ -22,16 +22,18 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.options.CurrentUserHolder;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.RoamingTypeDisabled;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ObjectUtils;
import com.intellij.util.PathUtilRt;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.SmartList;
import com.intellij.util.messages.MessageBus;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import gnu.trove.TObjectLongHashMap;
import org.jdom.Document;
import org.jdom.Element;
@@ -67,7 +69,6 @@ public abstract class StateStorageManagerImpl implements StateStorageManager, Di
private final Map<String, StateStorage> myPathToStorage = new THashMap<String, StateStorage>();
private final TrackingPathMacroSubstitutor myPathMacroSubstitutor;
private final String myRootTagName;
private Object mySession;
private final PicoContainer myPicoContainer;
private TObjectLongHashMap<String> myComponentVersions;
@@ -253,11 +254,11 @@ public abstract class StateStorageManagerImpl implements StateStorageManager, Di
@Nullable
private StateStorage createDirectoryStateStorage(String file, Class<? extends StateSplitter> splitterClass) {
StateSplitter splitter = ReflectionUtil.newInstance(splitterClass);
return new DirectoryBasedStorage(myPathMacroSubstitutor, expandMacros(file), splitter, this, myPicoContainer);
return new DirectoryBasedStorage(myPathMacroSubstitutor, expandMacros(file), splitter, this, createStorageTopicListener());
}
@Nullable
private StateStorage createFileStateStorage(@NotNull final String fileSpec, @Nullable RoamingType roamingType) {
private StateStorage createFileStateStorage(@NotNull String fileSpec, @Nullable RoamingType roamingType) {
String expandedFile = expandMacros(fileSpec);
if (!ourHeadlessEnvironment && PathUtilRt.getFileName(expandedFile).lastIndexOf('.') < 0) {
@@ -270,15 +271,30 @@ public abstract class StateStorageManagerImpl implements StateStorageManager, Di
beforeFileBasedStorageCreate();
return new FileBasedStorage(expandedFile, fileSpec, roamingType, getMacroSubstitutor(fileSpec), myRootTagName, this,
myPicoContainer, getStreamProvider(), this) {
createStorageTopicListener(), getStreamProvider(), this) {
@Override
@NotNull
protected StorageData createStorageData() {
return StateStorageManagerImpl.this.createStorageData(fileSpec);
return StateStorageManagerImpl.this.createStorageData(myFileSpec);
}
@Override
protected boolean isUseXmlProlog() {
return StateStorageManagerImpl.this.isUseXmlProlog();
}
};
}
@Nullable
protected StateStorage.Listener createStorageTopicListener() {
MessageBus messageBus = (MessageBus)myPicoContainer.getComponentInstanceOfType(MessageBus.class);
return messageBus == null ? null : messageBus.syncPublisher(StateStorage.STORAGE_TOPIC);
}
protected boolean isUseXmlProlog() {
return true;
}
protected void beforeFileBasedStorageCreate() {
}
@@ -355,7 +371,7 @@ public abstract class StateStorageManagerImpl implements StateStorageManager, Di
return myPathMacroSubstitutor;
}
protected abstract StorageData createStorageData(String storageSpec);
protected abstract StorageData createStorageData(@NotNull String storageSpec);
private static final Pattern MACRO_PATTERN = Pattern.compile("(\\$[^\\$]*\\$)");
@@ -390,47 +406,56 @@ public abstract class StateStorageManagerImpl implements StateStorageManager, Di
@NotNull
@Override
public ExternalizationSession startExternalization() {
if (mySession != null) {
LOG.error("Starting duplicate externalization session: " + mySession);
}
ExternalizationSession session = new MyExternalizationSession();
mySession = session;
return session;
return new StateStorageManagerExternalizationSession();
}
@NotNull
@Nullable
@Override
public SaveSession startSave(@NotNull ExternalizationSession externalizationSession) {
assert mySession == externalizationSession;
SaveSession session = new MySaveSession((MyExternalizationSession)externalizationSession);
mySession = session;
return session;
StateStorageManagerExternalizationSession myExternalizationSession = (StateStorageManagerExternalizationSession)externalizationSession;
Map<StateStorage, StateStorage.SaveSession> saveSessions = null;
for (StateStorage stateStorage : myExternalizationSession.mySessions.keySet()) {
StateStorage.SaveSession saveSession = stateStorage.startSave(myExternalizationSession.getExternalizationSession(stateStorage));
if (saveSession != null) {
if (saveSessions == null) {
saveSessions = new SmartHashMap<StateStorage, StateStorage.SaveSession>();
}
saveSessions.put(stateStorage, saveSession);
}
}
if (saveSessions == null) {
return null;
}
return new StateStorageSaveSession(saveSessions);
}
@Override
public void finishSave(@NotNull final SaveSession saveSession) {
try {
assert mySession == saveSession : "mySession=" + mySession + " saveSession=" + saveSession;
((MySaveSession)saveSession).finishSave();
public void finishSave(@NotNull SaveSession saveSession) {
if (!isDirty) {
return;
}
finally {
mySession = null;
save();
String filePath = getNotNullVersionsFilePath();
if (filePath != null) {
File file = new File(filePath);
FileUtilRt.createParentDirs(file);
try {
JDOMUtil.writeParent(createComponentVersionsXml(getComponentVersions()), file, "\n");
isDirty = false;
}
catch (IOException e) {
LOG.info(e);
}
}
}
@Override
public void reset() {
mySession = null;
}
protected class MyExternalizationSession implements ExternalizationSession {
private final class StateStorageManagerExternalizationSession implements ExternalizationSession {
final Map<StateStorage, StateStorage.ExternalizationSession> mySessions = new SmartHashMap<StateStorage, StateStorage.ExternalizationSession>();
@Override
public void setState(@NotNull Storage[] storageSpecs, @NotNull Object component, @NotNull String componentName, @NotNull Object state) {
assert mySession == this;
for (Storage storageSpec : storageSpecs) {
StateStorage stateStorage = getStateStorage(storageSpec);
if (stateStorage == null) {
@@ -443,7 +468,6 @@ public abstract class StateStorageManagerImpl implements StateStorageManager, Di
@Override
public void setStateInOldStorage(@NotNull Object component, @NotNull String componentName, @NotNull Object state) {
assert mySession == this;
StateStorage stateStorage = getOldStorage(component, componentName, StateStorageOperation.WRITE);
if (stateStorage != null) {
getExternalizationSession(stateStorage).setState(component, componentName, state, null);
@@ -471,73 +495,19 @@ public abstract class StateStorageManagerImpl implements StateStorageManager, Di
@Nullable
protected abstract String getOldStorageSpec(@NotNull Object component, @NotNull String componentName, @NotNull StateStorageOperation operation);
protected class MySaveSession implements SaveSession {
private final Map<StateStorage, StateStorage.SaveSession> mySaveSessions = new SmartHashMap<StateStorage, StateStorage.SaveSession>();
private final static class StateStorageSaveSession implements SaveSession {
private final Map<StateStorage, StateStorage.SaveSession> mySaveSessions;
public MySaveSession(@NotNull MyExternalizationSession externalizationSession) {
for (StateStorage stateStorage : externalizationSession.mySessions.keySet()) {
mySaveSessions.put(stateStorage, stateStorage.startSave(externalizationSession.getExternalizationSession(stateStorage)));
}
public StateStorageSaveSession(@NotNull Map<StateStorage, StateStorage.SaveSession> saveSessions) {
mySaveSessions = saveSessions;
}
@Override
public void collectAllStorageFiles(@NotNull List<VirtualFile> files) {
for (StateStorage.SaveSession saveSession : mySaveSessions.values()) {
saveSession.collectAllStorageFiles(files);
}
}
@Override
public void save() throws StateStorageException {
assert mySession == this;
public void save() {
for (StateStorage.SaveSession saveSession : mySaveSessions.values()) {
saveSession.save();
}
}
public void finishSave() {
RuntimeException re = null;
try {
LOG.assertTrue(mySession == this);
}
finally {
for (StateStorage stateStorage : mySaveSessions.keySet()) {
try {
stateStorage.finishSave(mySaveSessions.get(stateStorage));
}
catch (RuntimeException e) {
re = e;
}
}
}
if (re != null) {
throw re;
}
}
@Override
@Nullable
public Set<String> analyzeExternalChanges(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles) {
Set<String> result = null;
for (Pair<VirtualFile, StateStorage> pair : changedFiles) {
StateStorage.SaveSession saveSession = mySaveSessions.get(pair.second);
if (saveSession == null) {
continue;
}
Set<String> changes = saveSession.analyzeExternalChanges(changedFiles);
if (changes == null) {
return null;
}
if (result == null) {
result = new THashSet<String>();
}
result.addAll(changes);
}
return result == null ? Collections.<String>emptySet() : result;
}
}
@Override
@@ -556,25 +526,6 @@ public abstract class StateStorageManagerImpl implements StateStorageManager, Di
myStreamProvider = streamProvider;
}
public void save() {
if (!isDirty) {
return;
}
String filePath = getNotNullVersionsFilePath();
if (filePath != null) {
File file = new File(filePath);
FileUtilRt.createParentDirs(file);
try {
JDOMUtil.writeParent(createComponentVersionsXml(getComponentVersions()), file, "\n");
isDirty = false;
}
catch (IOException e) {
LOG.info(e);
}
}
}
private TObjectLongHashMap<String> getComponentVersions() {
synchronized (myComponentVersionsLock) {
if (myComponentVersions == null) {
@@ -19,7 +19,9 @@ import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.notification.NotificationType;
import com.intellij.notification.NotificationsManager;
import com.intellij.openapi.application.*;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.components.*;
import com.intellij.openapi.components.store.ReadOnlyModificationException;
import com.intellij.openapi.diagnostic.Logger;
@@ -35,8 +37,9 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileEvent;
import com.intellij.util.LineSeparator;
import com.intellij.util.SystemProperties;
import com.intellij.util.UniqueFileNamesProvider;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import org.jdom.Document;
@@ -48,11 +51,9 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.event.HyperlinkEvent;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.nio.ByteBuffer;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* @author mike
@@ -60,13 +61,17 @@ import java.util.Set;
public class StorageUtil {
private static final Logger LOG = Logger.getInstance(StorageUtil.class);
private static final boolean DUMP_COMPONENT_STATES = SystemProperties.getBooleanProperty("idea.log.externally.changed.component.states", false);
private static final byte[] XML_PROLOG = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>".getBytes(CharsetToolkit.UTF8_CHARSET);
@SuppressWarnings("SpellCheckingInspection")
private static final SimpleDateFormat LOG_DIR_FORMAT = new SimpleDateFormat("yyyyMMdd-HHmmss");
private static final Pair<byte[], String> NON_EXISTENT_FILE_DATA = Pair.create(null, SystemProperties.getLineSeparator());
private StorageUtil() { }
public static boolean isChangedByStorageOrSaveSession(@NotNull VirtualFileEvent event) {
return event.getRequestor() instanceof StateStorage.SaveSession || event.getRequestor() instanceof StateStorage;
}
public static void notifyUnknownMacros(@NotNull TrackingPathMacroSubstitutor substitutor,
@NotNull final Project project,
@Nullable String componentName) {
@@ -158,53 +163,58 @@ public class StorageUtil {
FileUtil.createParentDirs(file);
byteOut = writeToBytes(document, SystemProperties.getLineSeparator());
}
// mark this action as modifying the file which daemon analyzer should ignore
AccessToken token = ApplicationManager.getApplication().acquireWriteActionLock(DocumentRunnable.IgnoreDocumentRunnable.class);
try {
if (virtualFile == null) {
virtualFile = getOrCreateVirtualFile(requestor, file);
}
OutputStream virtualFileOut = virtualFile.getOutputStream(requestor);
try {
byteOut.writeTo(virtualFileOut);
}
finally {
virtualFileOut.close();
}
return virtualFile;
}
catch (FileNotFoundException e) {
if (virtualFile == null) {
throw e;
}
else {
throw new ReadOnlyModificationException(virtualFile);
}
}
finally {
token.finish();
}
return writeFile(file, requestor, virtualFile, byteOut, null);
}
catch (IOException e) {
throw new StateStorageException(e);
}
}
public static void deleteFile(@NotNull File file, @NotNull Object requestor, @Nullable VirtualFile cachedVirtualFile) throws IOException {
if (!file.exists()) {
return;
@NotNull
public static VirtualFile writeFile(@NotNull File file, @NotNull Object requestor, @Nullable VirtualFile virtualFile, @NotNull BufferExposingByteArrayOutputStream content, @Nullable LineSeparator lineSeparatorIfPrependXmlProlog) throws IOException {
// mark this action as modifying the file which daemon analyzer should ignore
AccessToken token = ApplicationManager.getApplication().acquireWriteActionLock(DocumentRunnable.IgnoreDocumentRunnable.class);
try {
if (virtualFile == null || !virtualFile.isValid()) {
virtualFile = getOrCreateVirtualFile(requestor, file);
}
OutputStream out = virtualFile.getOutputStream(requestor);
try {
if (lineSeparatorIfPrependXmlProlog != null) {
out.write(XML_PROLOG);
out.write(lineSeparatorIfPrependXmlProlog.getSeparatorBytes());
}
content.writeTo(out);
}
finally {
out.close();
}
return virtualFile;
}
catch (FileNotFoundException e) {
if (virtualFile == null) {
throw e;
}
else {
throw new ReadOnlyModificationException(virtualFile);
}
}
finally {
token.finish();
}
}
public static void deleteFile(@NotNull File file, @NotNull Object requestor, @Nullable VirtualFile virtualFile) throws IOException {
if (virtualFile == null) {
LOG.warn("Cannot find virtual file " + file.getAbsolutePath());
}
VirtualFile virtualFile = cachedVirtualFile;
if (virtualFile == null || !virtualFile.isValid()) {
virtualFile = LocalFileSystem.getInstance().findFileByIoFile(file);
}
if (virtualFile == null) {
LOG.info("Cannot find virtual file " + file.getAbsolutePath());
FileUtil.delete(file);
if (file.exists()) {
FileUtil.delete(file);
}
}
else {
else if (virtualFile.exists()) {
AccessToken token = ApplicationManager.getApplication().acquireWriteActionLock(DocumentRunnable.IgnoreDocumentRunnable.class);
try {
virtualFile.delete(requestor);
@@ -216,14 +226,14 @@ public class StorageUtil {
}
@NotNull
private static BufferExposingByteArrayOutputStream writeToBytes(@NotNull Parent element, @NotNull String lineSeparator) throws IOException {
public static BufferExposingByteArrayOutputStream writeToBytes(@NotNull Parent element, @NotNull String lineSeparator) throws IOException {
BufferExposingByteArrayOutputStream out = new BufferExposingByteArrayOutputStream(512);
JDOMUtil.writeParent(element, out, lineSeparator);
return out;
}
@NotNull
static VirtualFile getOrCreateVirtualFile(@Nullable Object requestor, @NotNull File ioFile) throws IOException {
private static VirtualFile getOrCreateVirtualFile(@Nullable Object requestor, @NotNull File ioFile) throws IOException {
VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(ioFile);
if (virtualFile == null) {
File parentFile = ioFile.getParentFile();
@@ -249,15 +259,26 @@ public class StorageUtil {
byte[] bytes = file.contentsToByteArray();
String lineSeparator = file.getDetectedLineSeparator();
if (lineSeparator == null) {
String fileText = new String(bytes, CharsetToolkit.UTF8);
final int index = fileText.indexOf('\n');
lineSeparator = index == -1
? SystemProperties.getLineSeparator()
: index - 1 >= 0 ? fileText.charAt(index - 1) == '\r' ? "\r\n" : "\n" : "\n";
lineSeparator = detectLineSeparators(CharsetToolkit.UTF8_CHARSET.decode(ByteBuffer.wrap(bytes)), null).getSeparatorString();
}
return Pair.create(bytes, lineSeparator);
}
@NotNull
public static LineSeparator detectLineSeparators(@NotNull CharSequence chars, @Nullable LineSeparator defaultSeparator) {
for (int i = 0, n = chars.length(); i < n; i++) {
char c = chars.charAt(i);
if (c == '\r') {
return LineSeparator.CRLF;
}
else if (c == '\n') {
// if we are here, there was no \r before
return LineSeparator.LF;
}
}
return defaultSeparator == null ? LineSeparator.getSystemLineSeparator() : defaultSeparator;
}
public static boolean contentEquals(@NotNull Parent element, @NotNull VirtualFile file) {
return newContentIfDiffers(element, file) == null;
}
@@ -359,71 +380,6 @@ public class StorageUtil {
provider.saveContent(fileSpec, content.getInternalBuffer(), content.size(), type, async);
}
public static void logStateDiffInfo(Set<Pair<VirtualFile, StateStorage>> changedFiles, Set<String> componentNames) {
if (componentNames.isEmpty() || !(DUMP_COMPONENT_STATES || ApplicationManager.getApplication().isInternal())) {
return;
}
try {
File logDirectory = createLogDirectory();
if (!logDirectory.mkdirs()) {
throw new IOException("Cannot create " + logDirectory);
}
for (Pair<VirtualFile, StateStorage> pair : changedFiles) {
File file = new File(pair.first.getPath());
StateStorage storage = pair.second;
if (storage instanceof XmlElementStorage) {
Element state = ((XmlElementStorage)storage).logComponents();
if (state != null) {
JDOMUtil.writeParent(state, new File(logDirectory, "prev_" + file.getName()), "\n");
}
}
if (file.exists()) {
File logFile = new File(logDirectory, "new_" + file.getName());
FileUtil.copy(file, logFile);
}
}
File logFile = new File(logDirectory, "components.txt");
FileUtil.writeToFile(logFile, componentNames.toString() + "\n");
}
catch (Throwable e) {
LOG.info(e);
}
}
private static File createLogDirectory() {
UniqueFileNamesProvider namesProvider = new UniqueFileNamesProvider();
File statesDir = new File(PathManager.getSystemPath(), "log/componentStates");
File[] children = statesDir.listFiles();
if (children != null) {
if (children.length > 10) {
File childToDelete = null;
for (File child : children) {
if (childToDelete == null || childToDelete.lastModified() > child.lastModified()) {
childToDelete = child;
}
}
if (childToDelete != null) {
FileUtil.delete(childToDelete);
}
}
for (File child : children) {
namesProvider.reserveFileName(child.getName());
}
}
String name = "state-" + LOG_DIR_FORMAT.format(new Date()) + "-" + ApplicationInfo.getInstance().getBuild().asString();
return new File(statesDir, namesProvider.suggestName(name));
}
public static boolean isProjectOrModuleFile(@NotNull String fileSpec) {
return StoragePathMacros.PROJECT_FILE.equals(fileSpec) || fileSpec.startsWith(StoragePathMacros.PROJECT_CONFIG_DIR) || fileSpec.equals("$MODULE_FILE$");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2011 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.
@@ -15,8 +15,13 @@
*/
package com.intellij.openapi.components.impl.stores;
import com.intellij.diagnostic.IdeErrorsDialog;
import com.intellij.diagnostic.PluginException;
import com.intellij.openapi.components.StateStorageException;
import com.intellij.openapi.components.store.ComponentSaveSession;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.ShutDownTracker;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
@@ -31,16 +36,38 @@ public class StoreUtil {
}
public static void doSave(@NotNull IComponentStore stateStore) {
ComponentSaveSession session = null;
ComponentSaveSession session = stateStore.startSave();
if (session == null) {
return;
}
ShutDownTracker.getInstance().registerStopperThread(Thread.currentThread());
try {
session = stateStore.startSave();
List<Pair<StateStorageManager.SaveSession, VirtualFile>> readonlyFiles = new SmartList<Pair<StateStorageManager.SaveSession, VirtualFile>>();
session.save(readonlyFiles);
}
catch (Throwable e) {
PluginId pluginId = IdeErrorsDialog.findPluginId(e);
if (pluginId == null) {
//noinspection InstanceofCatchParameter
if (e instanceof RuntimeException) {
throw ((RuntimeException)e);
}
else {
throw new StateStorageException(e);
}
}
else {
throw new PluginException(e, pluginId);
}
}
finally {
if (session != null) {
try {
session.finishSave();
}
finally {
ShutDownTracker.getInstance().unregisterStopperThread(Thread.currentThread());
}
}
}
}
@@ -23,9 +23,9 @@ import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
import com.intellij.openapi.vfs.VirtualFile;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import gnu.trove.TObjectLongHashMap;
import org.jdom.Document;
import org.jdom.Element;
@@ -33,10 +33,14 @@ import org.jdom.JDOMException;
import org.jdom.filter.ElementFilter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public abstract class XmlElementStorage implements StateStorage, Disposable {
private static final Logger LOG = Logger.getInstance(XmlElementStorage.class);
@@ -48,29 +52,17 @@ public abstract class XmlElementStorage implements StateStorage, Disposable {
protected TrackingPathMacroSubstitutor myPathMacroSubstitutor;
@NotNull protected final String myRootElementName;
private Object mySession;
protected StorageData myLoadedData;
protected final StreamProvider myStreamProvider;
protected final String myFileSpec;
protected boolean myBlockSavingTheContent = false;
protected int myUpToDateHash = -1;
private int myProviderUpToDateHash = -1;
private boolean mySavingDisabled = false;
private final Map<String, Object> myStorageComponentStates = new THashMap<String, Object>(); // at load we store Element, on setState Integer of hash
private final ComponentVersionProvider myLocalVersionProvider;
protected final RemoteComponentVersionProvider myRemoteVersionProvider;
private final RoamingType myRoamingType;
protected ComponentVersionListener myListener = new ComponentVersionListener(){
@Override
public void componentStateChanged(String componentName) {
myLocalVersionProvider.changeVersion(componentName, System.currentTimeMillis());
}
};
private boolean myDisposed;
protected XmlElementStorage(@NotNull String fileSpec,
@@ -98,28 +90,16 @@ public abstract class XmlElementStorage implements StateStorage, Disposable {
@Nullable
protected abstract Element loadLocalData();
@Nullable
public synchronized Element getState(@NotNull String componentName) {
final StorageData storageData = getStorageData(false);
final Element state = storageData.getState(componentName);
if (state != null) {
if (!myStorageComponentStates.containsKey(componentName)) {
myStorageComponentStates.put(componentName, state);
}
storageData.removeState(componentName);
}
return state;
}
@Override
public boolean hasState(final Object component, @NotNull String componentName, final Class<?> aClass, final boolean reloadData) throws StateStorageException {
public boolean hasState(@Nullable Object component, @NotNull String componentName, Class<?> aClass, boolean reloadData) throws StateStorageException {
return getStorageData(reloadData).hasState(componentName);
}
@Override
@Nullable
public <T> T getState(final Object component, @NotNull String componentName, Class<T> stateClass, @Nullable T mergeInto) throws StateStorageException {
return DefaultStateSerializer.deserializeState(getState(componentName), stateClass, mergeInto);
public <T> T getState(Object component, @NotNull String componentName, @NotNull Class<T> stateClass, @Nullable T mergeInto) throws StateStorageException {
Element state = getStorageData(false).getStateAndArchive(componentName);
return DefaultStateSerializer.deserializeState(state, stateClass, mergeInto);
}
@NotNull
@@ -196,109 +176,36 @@ public abstract class XmlElementStorage implements StateStorage, Disposable {
@Override
@NotNull
public ExternalizationSession startExternalization() {
ExternalizationSession session = new MyExternalizationSession(getStorageData().clone(), myListener);
mySession = session;
return session;
public final ExternalizationSession startExternalization() {
return createSaveSession(getStorageData());
}
@Nullable
@Override
@NotNull
public SaveSession startSave(@NotNull final ExternalizationSession externalizationSession) {
LOG.assertTrue(mySession == externalizationSession);
final SaveSession saveSession = mySavingDisabled ? createNullSession() : createSaveSession((MyExternalizationSession)externalizationSession);
mySession = saveSession;
return saveSession;
}
private static SaveSession createNullSession() {
return new SaveSession(){
@Override
public void save() {
}
@Override
public Set<String> analyzeExternalChanges(@NotNull final Set<Pair<VirtualFile, StateStorage>> changedFiles) {
return Collections.emptySet();
}
@Override
public void collectAllStorageFiles(@NotNull List<VirtualFile> files) {
}
};
}
protected abstract MySaveSession createSaveSession(MyExternalizationSession externalizationSession);
@Override
public void finishSave(@NotNull final SaveSession saveSession) {
try {
if (mySession != saveSession) {
LOG.error("mySession=" + mySession + " saveSession=" + saveSession);
}
} finally {
mySession = null;
public SaveSession startSave(@NotNull ExternalizationSession externalizationSession) {
if (mySavingDisabled) {
return null;
}
else {
MySaveSession session = (MySaveSession)externalizationSession;
return session.myCopiedStorageData == null ? null : session;
}
}
protected abstract MySaveSession createSaveSession(@NotNull StorageData storageData);
public void disableSaving() {
mySavingDisabled = true;
}
protected class MyExternalizationSession implements ExternalizationSession {
private final StorageData myStorageData;
private final ComponentVersionListener myListener;
public MyExternalizationSession(final StorageData storageData, ComponentVersionListener listener) {
myStorageData = storageData;
myListener = listener;
}
@Override
public void setState(@NotNull Object component, @NotNull String componentName, @NotNull Object state, @Nullable Storage storageSpec) {
assert mySession == this;
Element element;
try {
element = DefaultStateSerializer.serializeState(state, storageSpec);
}
catch (WriteExternalException e) {
LOG.debug(e);
return;
}
if (element == null || JDOMUtil.isEmpty(element)) {
return;
}
setState(componentName, element);
}
private synchronized void setState(@NotNull String componentName, @NotNull Element element) {
myStorageData.setState(componentName, element);
int hash = JDOMUtil.getTreeHash(element);
try {
Object oldElementState = myStorageComponentStates.get(componentName);
if (oldElementState instanceof Element && !JDOMUtil.areElementsEqual((Element)oldElementState, element) ||
oldElementState instanceof Integer && hash != (Integer)oldElementState) {
myListener.componentStateChanged(componentName);
}
}
finally {
myStorageComponentStates.put(componentName, hash);
}
}
}
@Nullable
protected Element getElement(@NotNull StorageData data) {
Element element = data.save();
protected final Element getElement(@NotNull StorageData data, boolean collapsePaths, @NotNull Map<String, Element> newLiveStates) {
Element element = data.save(newLiveStates);
if (element == null || JDOMUtil.isEmpty(element)) {
return null;
}
if (myPathMacroSubstitutor != null) {
if (collapsePaths && myPathMacroSubstitutor != null) {
try {
myPathMacroSubstitutor.collapsePaths(element);
}
@@ -310,95 +217,84 @@ public abstract class XmlElementStorage implements StateStorage, Disposable {
return element;
}
protected abstract class MySaveSession implements SaveSession {
final StorageData myStorageData;
private Element myElementToSave;
public MySaveSession(MyExternalizationSession externalizationSession) {
myStorageData = externalizationSession.myStorageData;
@Override
public void analyzeExternalChangesAndUpdateIfNeed(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles, @NotNull Set<String> result) {
StorageData oldData = myLoadedData;
StorageData newData = getStorageData(true);
if (oldData == null) {
result.addAll(newData.getComponentNames());
}
private boolean _needsSave(int hash) {
if (myBlockSavingTheContent) {
return false;
}
if (myUpToDateHash == -1) {
if (hash != -1) {
if (!physicalContentNeedsSave()) {
myUpToDateHash = hash;
return false;
}
else {
return true;
}
}
else {
return true;
}
}
else if (hash != -1) {
if (hash == myUpToDateHash) {
return false;
}
if (!physicalContentNeedsSave()) {
myUpToDateHash = hash;
return false;
}
else {
return true;
}
}
else {
return physicalContentNeedsSave();
else {
Set<String> changedComponentNames = oldData.getChangedComponentNames(newData, myPathMacroSubstitutor);
if (changedComponentNames != null) {
result.addAll(changedComponentNames);
}
}
}
protected boolean physicalContentNeedsSave() {
return true;
protected abstract class MySaveSession implements SaveSession, ExternalizationSession {
private final StorageData myOriginalStorageData;
private StorageData myCopiedStorageData;
private final Map<String, Element> myNewLiveStates = new THashMap<String, Element>();
public MySaveSession(@NotNull StorageData storageData) {
myOriginalStorageData = storageData;
}
protected abstract void doSave() throws StateStorageException;
@Override
public final void setState(@NotNull Object component, @NotNull String componentName, @NotNull Object state, @Nullable Storage storageSpec) {
Element element;
try {
element = DefaultStateSerializer.serializeState(state, storageSpec);
}
catch (WriteExternalException e) {
LOG.debug(e);
return;
}
catch (Throwable e) {
LOG.info("Unable to serialize component state!", e);
return;
}
protected int calcHash() {
return -1;
if (myCopiedStorageData == null) {
myCopiedStorageData = StorageData.setStateAndCloneIfNeed(componentName, element, myOriginalStorageData, myNewLiveStates);
if (myCopiedStorageData != null) {
myLocalVersionProvider.changeVersion(componentName, System.currentTimeMillis());
}
}
else if (myCopiedStorageData.setState(componentName, element, myNewLiveStates) != null) {
myLocalVersionProvider.changeVersion(componentName, System.currentTimeMillis());
}
}
@Override
public final void save() {
assert mySession == this;
if (myBlockSavingTheContent) {
return;
}
int hash = calcHash();
try {
if (myStreamProvider != null && myStreamProvider.isEnabled() && (myProviderUpToDateHash == -1 || myProviderUpToDateHash != hash)) {
try {
saveForProvider();
myProviderUpToDateHash = hash;
}
catch (Throwable e) {
LOG.error(e);
}
}
doSave(getElement(myCopiedStorageData, isCollapsePathsOnSave(), myNewLiveStates));
}
finally {
if (!(myUpToDateHash != -1 && myUpToDateHash == hash) && _needsSave(hash)) {
doSave();
}
myUpToDateHash = hash;
catch (IOException e) {
throw new StateStorageException(e);
}
}
private void saveForProvider() throws IOException {
// only because default project store hack
protected boolean isCollapsePathsOnSave() {
return true;
}
protected abstract void doSave(@Nullable Element element) throws IOException;
protected void saveForProvider(@Nullable BufferExposingByteArrayOutputStream content, @Nullable Element element) throws IOException {
if (!myStreamProvider.isApplicable(myFileSpec, myRoamingType)) {
return;
}
Element element = getElementToSave();
if (element == null || element.getChildren().isEmpty()) {
if (element == null) {
myStreamProvider.delete(myFileSpec, myRoamingType);
return;
}
@@ -411,68 +307,30 @@ public abstract class XmlElementStorage implements StateStorage, Disposable {
if (myFileSpec.equals(StoragePathMacros.WORKSPACE_FILE)) {
Element copiedElement = JDOMUtil.cloneElement(element, DISABLED_ROAMING_ELEMENT_FILTER);
if (copiedElement != null) {
doSaveForProvider(copiedElement, DISABLED_ROAMING_ELEMENT_FILTER.myRoamingType, myStreamProvider);
doSaveForProvider(copiedElement, DISABLED_ROAMING_ELEMENT_FILTER.myRoamingType, content);
}
}
else {
doSaveForProvider(element, myRoamingType, myStreamProvider);
doSaveForProvider(element, myRoamingType, content);
}
}
private void doSaveForProvider(@NotNull Element element, @NotNull RoamingType roamingType, @NotNull StreamProvider streamProvider) throws IOException {
StorageUtil.doSendContent(streamProvider, myFileSpec, element, roamingType, true);
if (streamProvider.isVersioningRequired()) {
private void doSaveForProvider(@NotNull Element element, @NotNull RoamingType roamingType, @Nullable BufferExposingByteArrayOutputStream content) throws IOException {
if (content == null) {
StorageUtil.doSendContent(myStreamProvider, myFileSpec, element, roamingType, true);
}
else {
myStreamProvider.saveContent(myFileSpec, content.getInternalBuffer(), content.size(), myRoamingType, true);
}
if (myStreamProvider.isVersioningRequired()) {
TObjectLongHashMap<String> versions = loadVersions(element.getChildren(StorageData.COMPONENT));
if (!versions.isEmpty()) {
Element versionDoc = StateStorageManagerImpl.createComponentVersionsXml(versions);
StorageUtil.doSendContent(streamProvider, myFileSpec + VERSION_FILE_SUFFIX, versionDoc, roamingType, true);
StorageUtil.doSendContent(myStreamProvider, myFileSpec + VERSION_FILE_SUFFIX, versionDoc, roamingType, true);
}
}
}
@Nullable
protected Element getElementToSave() {
if (myElementToSave == null) {
myElementToSave = getElement(myStorageData);
}
return myElementToSave;
}
public StorageData getData() {
return myStorageData;
}
@Override
@Nullable
public Set<String> analyzeExternalChanges(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles) {
boolean containsSelf = false;
for (Pair<VirtualFile, StateStorage> pair : changedFiles) {
if (pair.second == XmlElementStorage.this) {
containsSelf = true;
break;
}
}
if (!containsSelf) {
return Collections.emptySet();
}
try {
Element element = loadLocalData();
if (element == null) {
return Collections.emptySet();
}
StorageData storageData = createStorageData();
loadState(storageData, element);
return storageData.getChangedComponentNames(myStorageData, myPathMacroSubstitutor);
}
catch (StateStorageException e) {
LOG.info(e);
}
return null;
}
}
private TObjectLongHashMap<String> loadVersions(List<Element> elements) {
@@ -494,34 +352,11 @@ public abstract class XmlElementStorage implements StateStorage, Disposable {
myDisposed = true;
}
public void resetData(){
@TestOnly
public void resetData() {
myLoadedData = null;
}
@Override
public void reload(@NotNull final Set<String> changedComponents) {
final StorageData storageData = loadData(false);
final StorageData oldLoadedData = myLoadedData;
if (oldLoadedData != null) {
Set<String> componentsToRetain = new THashSet<String>(oldLoadedData.myComponentStates.keySet());
componentsToRetain.addAll(changedComponents);
// add empty configuration tags for removed components
for (String componentToRetain : componentsToRetain) {
if (!storageData.myComponentStates.containsKey(componentToRetain) && myStorageComponentStates.containsKey(componentToRetain)) {
Element emptyElement = new Element("component");
LOG.info("Create empty component element for " + componentsToRetain);
emptyElement.setAttribute(StorageData.NAME, componentToRetain);
storageData.myComponentStates.put(componentToRetain, emptyElement);
}
}
storageData.myComponentStates.keySet().retainAll(componentsToRetain);
}
myLoadedData = storageData;
}
private void filterOutOfDate(@NotNull Element element) {
if (myRemoteVersionProvider == null) {
return;
@@ -540,13 +375,7 @@ public abstract class XmlElementStorage implements StateStorage, Disposable {
}
}
@Nullable
Element logComponents() {
return mySession instanceof MySaveSession ? getElement(((MySaveSession)mySession).myStorageData) : null;
}
public void resetProviderCache() {
myProviderUpToDateHash = -1;
if (myRemoteVersionProvider != null) {
myRemoteVersionProvider.myProviderVersions = null;
}
@@ -15,26 +15,16 @@
*/
package com.intellij.openapi.components.store;
import com.intellij.openapi.components.StateStorage;
import com.intellij.openapi.components.impl.stores.StateStorageManager;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Set;
public interface ComponentSaveSession {
@NotNull
ComponentSaveSession save(@NotNull List<Pair<StateStorageManager.SaveSession, VirtualFile>> readonlyFiles);
void finishSave();
void reset();
@Nullable
Set<String> analyzeExternalChanges(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles);
void collectAllStorageFiles(boolean includingSubStructures, @NotNull List<VirtualFile> files);
}
@@ -50,6 +50,7 @@ public abstract class ProjectManagerEx extends ProjectManager {
public abstract void saveChangedProjectFile(@NotNull VirtualFile file, @NotNull Project project);
@Deprecated
public abstract boolean isFileSavedToBeReloaded(VirtualFile file);
public abstract void blockReloadingProjectOnExternalChanges();
@@ -32,9 +32,10 @@ import com.intellij.openapi.components.ExportableApplicationComponent;
import com.intellij.openapi.components.StateStorage;
import com.intellij.openapi.components.StateStorageException;
import com.intellij.openapi.components.TrackingPathMacroSubstitutor;
import com.intellij.openapi.components.impl.stores.FileBasedStorage;
import com.intellij.openapi.components.impl.stores.StateStorageManager;
import com.intellij.openapi.components.impl.stores.StorageUtil;
import com.intellij.openapi.components.impl.stores.XmlElementStorage;
import com.intellij.openapi.components.store.ComponentSaveSession;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.progress.*;
@@ -56,12 +57,11 @@ import com.intellij.util.ArrayUtil;
import com.intellij.util.SmartList;
import com.intellij.util.TimeoutUtil;
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 com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.UIUtil;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import gnu.trove.TObjectLongHashMap;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NonNls;
@@ -93,10 +93,7 @@ public class ProjectManagerImpl extends ProjectManagerEx implements NamedJDOMExt
private final Set<Project> myTestProjects = new THashSet<Project>();
private final Map<VirtualFile, byte[]> mySavedCopies = new THashMap<VirtualFile, byte[]>();
private final TObjectLongHashMap<VirtualFile> mySavedTimestamps = new TObjectLongHashMap<VirtualFile>();
private final Map<Project, List<Pair<VirtualFile, StateStorage>>> myChangedProjectFiles =
new THashMap<Project, List<Pair<VirtualFile, StateStorage>>>();
private final MultiMap<Project, ChangedFileEntry> myChangedProjectFiles = MultiMap.createSet();
private final Alarm myChangedFilesAlarm = new Alarm();
private final List<Pair<VirtualFile, StateStorage>> myChangedApplicationFiles = new SmartList<Pair<VirtualFile, StateStorage>>();
private final AtomicInteger myReloadBlockCount = new AtomicInteger(0);
@@ -130,11 +127,9 @@ public class ProjectManagerImpl extends ProjectManagerEx implements NamedJDOMExt
new ProjectManagerListener() {
@Override
public void projectOpened(final Project project) {
MessageBus messageBus = project.getMessageBus();
MessageBusConnection connection = messageBus.connect(project);
connection.subscribe(StateStorage.STORAGE_TOPIC, new StateStorage.Listener() {
project.getMessageBus().connect(project).subscribe(StateStorage.PROJECT_STORAGE_TOPIC, new StateStorage.Listener() {
@Override
public void storageFileChanged(@NotNull final VirtualFileEvent event, @NotNull final StateStorage storage) {
public void storageFileChanged(@NotNull VirtualFileEvent event, @NotNull StateStorage storage) {
projectStorageFileChanged(event, storage, project);
}
});
@@ -176,9 +171,23 @@ public class ProjectManagerImpl extends ProjectManagerEx implements NamedJDOMExt
registerExternalProjectFileListener(virtualFileManager);
}
static class ChangedFileEntry {
public VirtualFile file;
public StateStorage storage;
public long timestamp;
public byte[] savedContent;
public ChangedFileEntry(@NotNull VirtualFile file, @NotNull StateStorage storage) throws IOException {
this.file = file;
this.storage = storage;
timestamp = file.getTimeStamp();
savedContent = file.contentsToByteArray();
}
}
private void projectStorageFileChanged(@NotNull VirtualFileEvent event, @NotNull StateStorage storage, @Nullable Project project) {
VirtualFile file = event.getFile();
if (!file.isDirectory() && !(event.getRequestor() instanceof StateStorage.SaveSession)) {
if (!StorageUtil.isChangedByStorageOrSaveSession(event) && !file.isDirectory()) {
registerProjectToReload(project, file, storage);
}
}
@@ -622,22 +631,23 @@ public class ProjectManagerImpl extends ProjectManagerEx implements NamedJDOMExt
LOG.debug("[RELOAD] myReloadBlockCount = " + myReloadBlockCount.get());
if (myReloadBlockCount.get() == 0) {
Set<Project> projects;
synchronized (myChangedProjectFiles) {
if (myChangedProjectFiles.isEmpty()) return;
projects = new HashSet<Project>(myChangedProjectFiles.keySet());
if (myChangedProjectFiles.isEmpty()) {
return;
}
projects = new THashSet<Project>(myChangedProjectFiles.keySet());
}
List<Project> projectsToReload = new ArrayList<Project>();
List<Pair<Project, Collection<ChangedFileEntry>>> projectsToReload = new SmartList<Pair<Project, Collection<ChangedFileEntry>>>();
for (Project project : projects) {
if (shouldReloadProject(project)) {
projectsToReload.add(project);
Collection<ChangedFileEntry> changedFileEntries = shouldReloadProject(project);
if (changedFileEntries != null) {
projectsToReload.add(Pair.create(project, changedFileEntries));
}
}
for (final Project projectToReload : projectsToReload) {
reloadProjectImpl(projectToReload, false);
for (Pair<Project, Collection<ChangedFileEntry>> projectToReload : projectsToReload) {
reloadProjectImpl(projectToReload.first, projectToReload.second);
}
}
}
@@ -699,39 +709,41 @@ public class ProjectManagerImpl extends ProjectManagerEx implements NamedJDOMExt
}
}
private boolean shouldReloadProject(final Project project) {
if (project.isDisposed()) return false;
final HashSet<Pair<VirtualFile, StateStorage>> causes = new HashSet<Pair<VirtualFile, StateStorage>>();
synchronized (myChangedProjectFiles) {
final List<Pair<VirtualFile, StateStorage>> changes = myChangedProjectFiles.remove(project);
if (changes != null) {
causes.addAll(changes);
}
if (causes.isEmpty()) return false;
@Nullable
private Collection<ChangedFileEntry> shouldReloadProject(@NotNull Project project) {
if (project.isDisposed()) {
return null;
}
final boolean[] reloadOk = {false};
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
try {
LOG.debug("[RELOAD] Reloading project/components...");
reloadOk[0] = ((ProjectEx)project).getStateStore().reload(causes);
}
catch (StateStorageException e) {
Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()),
ProjectBundle.message("project.reload.failed.title"));
}
catch (IOException e) {
Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()),
ProjectBundle.message("project.reload.failed.title"));
final Set<Pair<VirtualFile, StateStorage>> causes = new SmartHashSet<Pair<VirtualFile, StateStorage>>();
Collection<ChangedFileEntry> changes;
synchronized (myChangedProjectFiles) {
changes = myChangedProjectFiles.remove(project);
if (!ContainerUtil.isEmpty(changes)) {
for (ChangedFileEntry change : changes) {
causes.add(Pair.create(change.file, change.storage));
}
}
});
if (reloadOk[0]) return false;
}
if (causes.isEmpty()) {
return null;
}
AccessToken token = WriteAction.start();
try {
LOG.debug("[RELOAD] Reloading project/components...");
if (((ProjectEx)project).getStateStore().reload(causes)) {
return null;
}
}
catch (StateStorageException e) {
Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()),
ProjectBundle.message("project.reload.failed.title"));
}
finally {
token.finish();
}
String message;
if (causes.size() == 1) {
@@ -757,16 +769,26 @@ public class ProjectManagerImpl extends ProjectManagerEx implements NamedJDOMExt
message = ProjectBundle.message("project.reload.external.change.multiple", filesBuilder.toString());
}
return Messages.showDialog(message,
ProjectBundle.message("project.reload.external.change.title"),
new String[]{"&Reload Project", "&Discard Changes"},
-1,
Messages.getQuestionIcon()) == 0;
if (Messages.showDialog(message,
ProjectBundle.message("project.reload.external.change.title"),
new String[]{"&Reload Project", "&Discard Changes"},
-1,
Messages.getQuestionIcon()) == 0) {
return changes;
}
else {
return null;
}
}
@Override
public boolean isFileSavedToBeReloaded(VirtualFile candidate) {
return mySavedCopies.containsKey(candidate);
for (ChangedFileEntry entry : myChangedProjectFiles.values()) {
if (entry.file.equals(candidate)) {
return true;
}
}
return false;
}
@Override
@@ -822,40 +844,33 @@ public class ProjectManagerImpl extends ProjectManagerEx implements NamedJDOMExt
}
@Override
public void saveChangedProjectFile(@NotNull VirtualFile file, @Nullable Project project) {
registerProjectToReload(project, file, null);
public void saveChangedProjectFile(@NotNull VirtualFile file, @NotNull Project project) {
StateStorageManager storageManager = ((ProjectEx)project).getStateStore().getStateStorageManager();
String fileSpec = storageManager.collapseMacros(file.getPath());
Couple<Collection<FileBasedStorage>> storages = storageManager.getCachedFileStateStorages(Collections.singletonList(fileSpec), Collections.<String>emptyList());
FileBasedStorage storage = ContainerUtil.getFirstItem(storages.first);
// if empty, so, storage is not yet loaded, so, we don't have to reload
if (storage != null) {
registerProjectToReload(project, file, storage);
}
}
private void registerProjectToReload(@Nullable Project project, VirtualFile cause, @Nullable StateStorage storage) {
if (cause.exists()) {
try {
byte[] bytes = cause.contentsToByteArray();
mySavedCopies.put(cause, bytes);
mySavedTimestamps.put(cause, cause.getTimeStamp());
}
catch (IOException e) {
LOG.error(e);
}
}
private void registerProjectToReload(@Nullable Project project, @NotNull VirtualFile cause, @NotNull StateStorage storage) {
if (LOG.isDebugEnabled()) {
LOG.debug("[RELOAD] Registering project to reload: " + cause, new Exception());
}
if (project != null) {
synchronized (myChangedProjectFiles) {
List<Pair<VirtualFile, StateStorage>> changedProjectFiles = myChangedProjectFiles.get(project);
if (changedProjectFiles == null) {
changedProjectFiles = new SmartList<Pair<VirtualFile, StateStorage>>();
myChangedProjectFiles.put(project, changedProjectFiles);
}
changedProjectFiles.add(Pair.create(cause, storage));
}
}
else {
if (project == null) {
myChangedApplicationFiles.add(Pair.create(cause, storage));
}
else if (cause.exists()) {
try {
myChangedProjectFiles.putValue(project, new ChangedFileEntry(cause, storage));
}
catch (IOException e) {
LOG.error(e);
}
}
myChangedFilesAlarm.cancelAllRequests();
myChangedFilesAlarm.addRequest(new Runnable() {
@@ -869,45 +884,25 @@ public class ProjectManagerImpl extends ProjectManagerEx implements NamedJDOMExt
}, 444);
}
private void restoreCopy(VirtualFile file) {
try {
if (file == null) return; // Externally deleted actually.
if (!file.isWritable()) return; // IDEA was unable to save it as well. So no need to restore.
final byte[] bytes = mySavedCopies.get(file);
if (bytes != null) {
try {
file.setBinaryContent(bytes, -1, mySavedTimestamps.get(file));
}
catch (IOException e) {
Messages.showWarningDialog(ProjectBundle.message("project.reload.write.failed", file.getPresentableUrl()),
ProjectBundle.message("project.reload.write.failed.title"));
}
}
}
finally {
mySavedCopies.remove(file);
mySavedTimestamps.remove(file);
}
}
@Override
public void reloadProject(@NotNull final Project p) {
reloadProjectImpl(p, true);
public void reloadProject(@NotNull Project p) {
myChangedProjectFiles.remove(p);
reloadProjectImpl(p, Collections.<ChangedFileEntry>emptyList());
}
public void reloadProjectImpl(@NotNull final Project p, final boolean clearCopyToRestore) {
if (clearCopyToRestore) {
mySavedCopies.clear();
mySavedTimestamps.clear();
public void reloadProjectImpl(@NotNull Project p, @Nullable Collection<ChangedFileEntry> changedFileEntries) {
final Collection<ChangedFileEntry> filesToRestore;
if (changedFileEntries == null) {
// our internal ReloadProjectAction
filesToRestore = myChangedProjectFiles.remove(p);
}
else {
filesToRestore = changedFileEntries;
}
final Project[] project = {p};
ProjectReloadState.getInstance(project[0]).onBeforeAutomaticProjectReload();
final Application application = ApplicationManager.getApplication();
application.invokeLater(new Runnable() {
ProjectReloadState.getInstance(p).onBeforeAutomaticProjectReload();
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
LOG.debug("Reloading project.");
@@ -916,26 +911,28 @@ public class ProjectManagerImpl extends ProjectManagerEx implements NamedJDOMExt
return;
}
final String location = projectImpl.getPresentableUrl();
final List<VirtualFile> original = new SmartList<VirtualFile>();
try {
ComponentSaveSession saveSession = projectImpl.getStateStore().startSave();
saveSession.collectAllStorageFiles(true, original);
saveSession.finishSave();
}
catch (Exception e) {
LOG.error(e);
return;
}
if (project[0].isDisposed() || ProjectUtil.closeAndDispose(project[0])) {
application.runWriteAction(new Runnable() {
@Override
public void run() {
for (VirtualFile originalFile : original) {
restoreCopy(originalFile);
if (!ContainerUtil.isEmpty(filesToRestore)) {
AccessToken token = WriteAction.start();
try {
for (ChangedFileEntry entry : filesToRestore) {
if (!entry.file.isWritable()) {
continue; // IDEA was unable to save it as well. So no need to restore.
}
try {
entry.file.setBinaryContent(entry.savedContent, -1, entry.timestamp);
}
catch (IOException e) {
Messages.showWarningDialog(ProjectBundle.message("project.reload.write.failed", entry.file.getPresentableUrl()),
ProjectBundle.message("project.reload.write.failed.title"));
}
}
}
});
finally {
token.finish();
}
}
project[0] = null; // Let it go.
@@ -1133,7 +1130,7 @@ public class ProjectManagerImpl extends ProjectManagerEx implements NamedJDOMExt
}
}
public void setDefaultProjectRootElement(final Element defaultProjectRootElement) {
public void setDefaultProjectRootElement(@Nullable Element defaultProjectRootElement) {
myDefaultProjectRootElement = defaultProjectRootElement;
}
@@ -9,6 +9,7 @@ import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.testFramework.LightPlatformLangTestCase;
import com.intellij.util.messages.MessageBus;
import com.intellij.util.xmlb.XmlSerializerUtil;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NotNull;
@@ -137,7 +138,7 @@ public class ApplicationStoreTest extends LightPlatformLangTestCase {
TrackingPathMacroSubstitutor macroSubstitutor = new ApplicationPathMacroManager().createTrackingSubstitutor();
stateStorageManager = new StateStorageManagerImpl(macroSubstitutor, "application", this, ApplicationManager.getApplication().getPicoContainer()) {
@Override
protected StorageData createStorageData(String storageSpec) {
protected StorageData createStorageData(@NotNull String storageSpec) {
return new FileBasedStorage.FileStorageData("application");
}
@@ -183,6 +184,12 @@ public class ApplicationStoreTest extends LightPlatformLangTestCase {
protected StateStorage getDefaultsStorage() {
return null;
}
@NotNull
@Override
protected MessageBus getMessageBus() {
return ApplicationManager.getApplication().getMessageBus();
}
}
static class SeveralStoragesConfiguredStorageChooser implements StateStorageChooser<SeveralStoragesConfigured> {
@@ -41,7 +41,7 @@ public class StateStorageManagerImplTest extends LightPlatformLangTestCase {
super.setUp();
myStateStorageManager = new StateStorageManagerImpl(null, "foo", null, ApplicationManager.getApplication().getPicoContainer()) {
@Override
protected StorageData createStorageData(String storageSpec) {
protected StorageData createStorageData(@NotNull String storageSpec) {
throw new UnsupportedOperationException("Method createStorageData not implemented in " + getClass());
}
@@ -21,9 +21,9 @@ import com.intellij.openapi.components.StateStorage;
import com.intellij.openapi.components.StateStorageException;
import com.intellij.openapi.components.TrackingPathMacroSubstitutor;
import com.intellij.openapi.components.impl.stores.ComponentVersionProvider;
import com.intellij.openapi.components.impl.stores.StorageData;
import com.intellij.openapi.components.impl.stores.XmlElementStorage;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.LightPlatformLangTestCase;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
@@ -31,7 +31,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static com.intellij.openapi.util.JDOMBuilder.attr;
@@ -98,16 +97,11 @@ public class XmlElementStorageTest extends LightPlatformLangTestCase {
}
@Override
protected MySaveSession createSaveSession(final MyExternalizationSession externalizationSession) {
return new MySaveSession(externalizationSession) {
protected MySaveSession createSaveSession(@NotNull StorageData storageData) {
return new MySaveSession(storageData) {
@Override
protected void doSave() throws StateStorageException {
Element elementToSave = getElementToSave();
mySavedElement = elementToSave == null ? null : elementToSave.clone();
}
@Override
public void collectAllStorageFiles(@NotNull List<VirtualFile> files) {
protected void doSave(@Nullable Element element) {
mySavedElement = element == null ? null : element.clone();
}
};
}
@@ -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.
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.components;
import com.intellij.openapi.util.Pair;
@@ -23,26 +22,33 @@ import com.intellij.util.messages.Topic;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Set;
public interface StateStorage {
Topic<Listener> STORAGE_TOPIC = new Topic<Listener>("STORAGE_LISTENER", Listener.class, Topic.BroadcastDirection.TO_PARENT);
// app storage files changed
Topic<Listener> STORAGE_TOPIC = new Topic<Listener>("STORAGE_LISTENER", Listener.class, Topic.BroadcastDirection.NONE);
// project storage files changes (project or modules, it is reason why we use broadcast TO_PARENT - to be notified when some module storage file changed
// even if listen only project message bus)
Topic<Listener> PROJECT_STORAGE_TOPIC = new Topic<Listener>("PROJECT_STORAGE_LISTENER", Listener.class, Topic.BroadcastDirection.NONE);
@Nullable
<T> T getState(final Object component, @NotNull String componentName, Class<T> stateClass, @Nullable T mergeInto) throws StateStorageException;
<T> T getState(@Nullable Object component, @NotNull String componentName, @NotNull Class<T> stateClass, @Nullable T mergeInto) throws StateStorageException;
boolean hasState(final Object component, @NotNull String componentName, final Class<?> aClass, final boolean reloadData) throws StateStorageException;
boolean hasState(@Nullable Object component, @NotNull String componentName, final Class<?> aClass, final boolean reloadData) throws StateStorageException;
@NotNull
ExternalizationSession startExternalization();
@NotNull
/**
* return null if nothing to save
*/
@Nullable
SaveSession startSave(@NotNull ExternalizationSession externalizationSession);
void finishSave(@NotNull SaveSession saveSession);
void reload(@NotNull Set<String> changedComponents);
/**
* Get changed component names
*/
void analyzeExternalChangesAndUpdateIfNeed(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles, @NotNull Set<String> result);
interface ExternalizationSession {
void setState(@NotNull Object component, @NotNull String componentName, @NotNull Object state, @Nullable Storage storageSpec);
@@ -50,14 +56,6 @@ public interface StateStorage {
interface SaveSession {
void save();
/**
* Get changed component names
*/
@Nullable
Set<String> analyzeExternalChanges(@NotNull Set<Pair<VirtualFile, StateStorage>> changedFiles);
void collectAllStorageFiles(@NotNull List<VirtualFile> files);
}
interface Listener {
@@ -12,6 +12,7 @@
<orderEntry type="module" module-name="jps-model-api" />
<orderEntry type="module" module-name="jps-model-impl" />
<orderEntry type="module" module-name="jps-model-serialization" exported="" />
<orderEntry type="library" name="Snappy-Java" level="project" />
</component>
</module>
@@ -23,11 +23,12 @@ import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtilRt;
import gnu.trove.THashSet;
import com.intellij.util.containers.SmartHashSet;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
@@ -54,10 +55,16 @@ public class PathMacrosCollector extends PathMacroMap {
PathMacros.getInstance());
}
public static Set<String> getMacroNames(Element root, @Nullable PathMacroFilter filter, @NotNull final PathMacros pathMacros) {
@NotNull
public static Set<String> getMacroNames(Element root, @Nullable PathMacroFilter filter, @NotNull PathMacros pathMacros) {
final PathMacrosCollector collector = new PathMacrosCollector();
collector.substitute(root, true, false, filter);
final Set<String> result = new THashSet<String>(collector.myMacroMap.keySet());
Set<String> preResult = collector.myMacroMap.keySet();
if (preResult.isEmpty()) {
return Collections.emptySet();
}
Set<String> result = new SmartHashSet<String>(preResult);
result.removeAll(pathMacros.getSystemMacroNames());
result.removeAll(pathMacros.getLegacyMacroNames());
result.removeAll(PathMacrosImpl.getToolMacroNames());
@@ -23,6 +23,7 @@ import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.JDOMExternalizable;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.xmlb.Accessor;
import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters;
import com.intellij.util.xmlb.XmlSerializer;
@@ -41,45 +42,31 @@ class DefaultStateSerializer {
}
@Nullable
static Element serializeState(@NotNull Object state, final Storage storage) throws WriteExternalException {
static Element serializeState(@NotNull Object state, @Nullable final Storage storage) throws WriteExternalException {
if (state instanceof Element) {
return (Element)state;
}
else if (state instanceof JDOMExternalizable) {
JDOMExternalizable jdomExternalizable = (JDOMExternalizable)state;
final Element element = new Element("temp_element");
try {
jdomExternalizable.writeExternal(element);
}
catch (WriteExternalException e) {
throw e;
}
catch (Throwable e) {
LOG.info("Unable to serialize component state!", e);
return null;
}
Element element = new Element("temp_element");
((JDOMExternalizable)state).writeExternal(element);
return element;
}
else {
return XmlSerializer.serialize(state, new SkipDefaultValuesSerializationFilters() {
@Override
public boolean accepts(final Accessor accessor, final Object bean) {
if (!super.accepts(accessor, bean)) return false;
if (storage != null) {
final Annotation[] annotations = accessor.getAnnotations();
for (Annotation annotation : annotations) {
if (StorageId.class.isAssignableFrom(annotation.annotationType())) {
StorageId storageId = (StorageId)annotation;
if (!storageId.value().equals(storage.id())) return false;
}
}
return storage.isDefault();
if (!super.accepts(accessor, bean)) {
return false;
}
if (storage != null) {
for (Annotation annotation : accessor.getAnnotations()) {
if (StorageId.class.isAssignableFrom(annotation.annotationType()) && !((StorageId)annotation).value().equals(storage.id())) {
return false;
}
}
return storage.isDefault();
}
return true;
}
});
@@ -100,32 +87,21 @@ class DefaultStateSerializer {
String elementText = JDOMUtil.writeElement(stateElement, "\n");
LOG.error("State is " + stateClass.getName() + ", merge into is " + mergeInto.toString() + ", state element text is " + elementText);
}
final T t = ReflectionUtil.newInstance(stateClass);
try {
final T t = stateClass.newInstance();
try {
((JDOMExternalizable)t).readExternal(stateElement);
return t;
}
catch (InvalidDataException e) {
throw new StateStorageException(e);
}
((JDOMExternalizable)t).readExternal(stateElement);
return t;
}
catch (InstantiationException e) {
throw new StateStorageException(e);
}
catch (IllegalAccessException e) {
catch (InvalidDataException e) {
throw new StateStorageException(e);
}
}
else if (mergeInto == null) {
return XmlSerializer.deserialize(stateElement, stateClass);
}
else {
if (mergeInto == null) {
return XmlSerializer.deserialize(stateElement, stateClass);
}
else {
XmlSerializer.deserializeInto(mergeInto, stateElement);
return mergeInto;
}
XmlSerializer.deserializeInto(mergeInto, stateElement);
return mergeInto;
}
}
}
@@ -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.
@@ -18,16 +18,22 @@ package com.intellij.openapi.components.impl.stores;
import com.intellij.application.options.PathMacrosCollector;
import com.intellij.ide.plugins.IdeaPluginDescriptorImpl;
import com.intellij.openapi.components.PathMacroSubstitutor;
import com.intellij.openapi.components.StateStorageException;
import com.intellij.openapi.components.TrackingPathMacroSubstitutor;
import com.intellij.openapi.components.XmlConfigurationMerger;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.ExtensionPoint;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtil;
import com.intellij.util.SystemProperties;
import com.intellij.util.containers.SmartHashSet;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.iq80.snappy.SnappyInputStream;
import org.iq80.snappy.SnappyOutputStream;
import org.jdom.Attribute;
import org.jdom.Element;
import org.jdom.JDOMException;
@@ -35,27 +41,35 @@ import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
public class StorageData {
private static final Logger LOG = Logger.getInstance(StorageData.class);
@NonNls public static final String COMPONENT = "component";
@NonNls public static final String NAME = "name";
final Map<String, Element> myComponentStates;
private final Map<String, Object> myComponentStates;
protected final String myRootElementName;
private int myHash = -1;
public StorageData(@NotNull String rootElementName) {
myComponentStates = new THashMap<String, Element>();
myComponentStates = new THashMap<String, Object>();
myRootElementName = rootElementName;
}
StorageData(@NotNull StorageData storageData) {
myRootElementName = storageData.myRootElementName;
myComponentStates = new THashMap<String, Element>(storageData.myComponentStates);
myComponentStates = new THashMap<String, Object>(storageData.myComponentStates);
}
@NotNull
public Set<String> getComponentNames() {
return myComponentStates.keySet();
}
public void load(@NotNull Element rootElement, @Nullable PathMacroSubstitutor pathMacroSubstitutor, boolean intern) {
@@ -66,32 +80,31 @@ public class StorageData {
for (Iterator<Element> iterator = rootElement.getChildren(COMPONENT).iterator(); iterator.hasNext(); ) {
Element element = iterator.next();
String name = element.getAttributeValue(NAME);
if (name == null) {
LOG.info("Broken content in file : " + this);
if (StringUtil.isEmpty(name)) {
LOG.warn("No name attribute for component in " + this);
continue;
}
if (element.getAttributes().size() > 1 || !element.getChildren().isEmpty()) {
assert element.getAttributeValue(NAME) != null : "No name attribute for component: " + name + " in " + this;
iterator.remove();
if (intern) {
IdeaPluginDescriptorImpl.internJDOMElement(element);
}
Element serverElement = myComponentStates.get(name);
Object serverElement = myComponentStates.get(name);
if (serverElement != null) {
element = mergeElements(name, element, serverElement);
element = mergeElements(name, element, (Element)serverElement);
}
myComponentStates.put(name, element);
}
}
if (pathMacroSubstitutor instanceof TrackingPathMacroSubstitutor) {
for (String componentName : myComponentStates.keySet()) {
((TrackingPathMacroSubstitutor)pathMacroSubstitutor).addUnknownMacros(componentName, PathMacrosCollector.getMacroNames(myComponentStates.get(componentName)));
if (pathMacroSubstitutor instanceof TrackingPathMacroSubstitutor) {
((TrackingPathMacroSubstitutor)pathMacroSubstitutor).addUnknownMacros(name, PathMacrosCollector.getMacroNames(element));
}
}
// remove only after "getMacroNames" - some PathMacroFilter requires element name attribute
element.removeAttribute(NAME);
}
}
@@ -106,8 +119,9 @@ public class StorageData {
return serverElement;
}
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
@Nullable
protected Element save() {
protected Element save(@NotNull Map<String, Element> newLiveStates) {
if (myComponentStates.isEmpty()) {
return null;
}
@@ -117,55 +131,212 @@ public class StorageData {
Arrays.sort(componentNames);
for (String componentName : componentNames) {
assert componentName != null;
final Element element = myComponentStates.get(componentName);
Element element;
Object state = myComponentStates.get(componentName);
if (state instanceof Element) {
element = ((Element)state).clone();
}
else {
element = newLiveStates.get(componentName);
if (element == null) {
try {
element = unarchiveState((byte[])state);
}
catch (IOException e) {
throw new StateStorageException(e);
}
catch (JDOMException e) {
throw new StateStorageException(e);
}
}
}
if (element.getAttribute(NAME) == null) element.setAttribute(NAME, componentName);
// name attribute should be first
assert element != null;
List<Attribute> elementAttributes = element.getAttributes();
if (elementAttributes.isEmpty()) {
element.setAttribute(NAME, componentName);
}
else {
Attribute nameAttribute = element.getAttribute(NAME);
if (nameAttribute == null) {
nameAttribute = new Attribute(NAME, componentName);
elementAttributes.add(0, nameAttribute);
}
else {
nameAttribute.setValue(componentName);
if (elementAttributes.get(0) != nameAttribute) {
elementAttributes.remove(nameAttribute);
elementAttributes.add(0, nameAttribute);
}
}
}
rootElement.addContent(element.clone());
rootElement.addContent(element);
}
return rootElement;
}
@Nullable
public Element getState(final String name) {
final Element element = myComponentStates.get(name);
if (element != null) {
assert element.getAttributeValue(NAME) != null : "No name attribute for component: " + name + " in " + this;
element.removeAttribute(NAME);
}
return element;
public Element getState(@NotNull String name) {
Object state = myComponentStates.get(name);
return state instanceof Element ? (Element)state : null;
}
void removeState(final String componentName) {
myComponentStates.remove(componentName);
clearHash();
@Nullable
public Element getStateAndArchive(@NotNull String name) {
Object state = myComponentStates.get(name);
if (!(state instanceof Element)) {
return null;
}
myComponentStates.put(name, archiveState((Element)state));
return (Element)state;
}
void setState(@NotNull String componentName, @NotNull Element element) {
element.setName(COMPONENT);
// componentName should be first
List<Attribute> elementAttributes = element.getAttributes();
if (elementAttributes.isEmpty()) {
element.setAttribute(NAME, componentName);
}
else {
Attribute nameAttribute = element.getAttribute(NAME);
if (nameAttribute == null) {
nameAttribute = new Attribute(NAME, componentName);
elementAttributes.add(0, nameAttribute);
@NotNull
private static byte[] archiveState(@NotNull Element state) {
BufferExposingByteArrayOutputStream byteOut = new BufferExposingByteArrayOutputStream();
OutputStream out = null;
try {
try {
out = new SnappyOutputStream(byteOut);
JDOMUtil.writeParent(state, out, "\n");
}
else {
nameAttribute.setValue(componentName);
if (elementAttributes.get(0) != nameAttribute) {
elementAttributes.remove(nameAttribute);
elementAttributes.add(0, nameAttribute);
finally {
if (out != null) {
out.close();
}
}
}
catch (IOException e) {
throw new StateStorageException(e);
}
return ArrayUtil.realloc(byteOut.getInternalBuffer(), byteOut.size());
}
myComponentStates.put(componentName, element);
clearHash();
@NotNull
private static Element unarchiveState(@NotNull byte[] state) throws IOException, JDOMException {
InputStream in = null;
try {
in = new SnappyInputStream(new ByteArrayInputStream(state));
//noinspection ConstantConditions
return JDOMUtil.loadDocument(in).detachRootElement();
}
finally {
if (in != null) {
in.close();
}
}
}
@Nullable
static StorageData setStateAndCloneIfNeed(@NotNull String componentName, @Nullable Element newState, @NotNull StorageData storageData, @NotNull Map<String, Element> newLiveStates) {
Object oldState = storageData.myComponentStates.get(componentName);
if (newState == null || JDOMUtil.isEmpty(newState)) {
if (oldState == null) {
return null;
}
StorageData newStorageData = storageData.clone();
newStorageData.myComponentStates.remove(componentName);
return newStorageData;
}
prepareElement(newState);
newLiveStates.put(componentName, newState);
byte[] newBytes = null;
if (oldState instanceof Element) {
if (JDOMUtil.areElementsEqual((Element)oldState, newState)) {
return null;
}
}
else if (oldState != null) {
newBytes = getNewByteIfDiffers(componentName, newState, (byte[])oldState);
if (newBytes == null) {
return null;
}
}
StorageData newStorageData = storageData.clone();
newStorageData.myComponentStates.put(componentName, newBytes == null ? newState : newBytes);
return newStorageData;
}
@Nullable
final Object setState(@NotNull String componentName, @Nullable Element newState, @NotNull Map<String, Element> newLiveStates) {
if (newState == null || JDOMUtil.isEmpty(newState)) {
return myComponentStates.remove(componentName);
}
prepareElement(newState);
newLiveStates.put(componentName, newState);
Object oldState = myComponentStates.get(componentName);
byte[] newBytes = null;
if (oldState instanceof Element) {
if (JDOMUtil.areElementsEqual((Element)oldState, newState)) {
return null;
}
}
else if (oldState != null) {
newBytes = getNewByteIfDiffers(componentName, newState, (byte[])oldState);
if (newBytes == null) {
return null;
}
}
myComponentStates.put(componentName, newBytes == null ? newState : newBytes);
return newState;
}
@Nullable
private static byte[] getNewByteIfDiffers(@NotNull String componentName, @NotNull Object newState, @NotNull byte[] oldState) {
byte[] newBytes = newState instanceof Element ? archiveState((Element)newState) : (byte[])newState;
if (Arrays.equals(newBytes, oldState)) {
return null;
}
else if (LOG.isDebugEnabled() && SystemProperties.getBooleanProperty("idea.log.changed.components", false)) {
String before = stateToString(oldState);
String after = stateToString(newState);
if (before.equals(after)) {
LOG.debug("Serialization error: serialized are different, but unserialized are equal");
}
else {
LOG.debug(componentName + " " + StringUtil.repeat("=", 80 - componentName.length()) + "\nBefore:\n" + before + "\nAfter:\n" + after);
}
}
return newBytes;
}
@NotNull
private static String stateToString(@NotNull Object state) {
Element element;
if (state instanceof Element) {
element = (Element)state;
}
else {
try {
element = unarchiveState((byte[])state);
}
catch (IOException e) {
LOG.error(e);
return "internal error";
}
catch (JDOMException e) {
LOG.error(e);
return "internal error";
}
}
return JDOMUtil.writeParent(element, "\n");
}
private static void prepareElement(@NotNull Element element) {
element.setName(COMPONENT);
}
@Override
@@ -173,52 +344,28 @@ public class StorageData {
return new StorageData(this);
}
public final int getHash() {
if (myHash == -1) {
myHash = computeHash();
if (myHash == -1) {
myHash = 0;
}
}
return myHash;
}
// newStorageData - myComponentStates contains only live (unarchived) states
public Set<String> getChangedComponentNames(@NotNull StorageData newStorageData, @Nullable PathMacroSubstitutor substitutor) {
Set<String> bothStates = new SmartHashSet<String>(myComponentStates.keySet());
bothStates.retainAll(newStorageData.myComponentStates.keySet());
protected int computeHash() {
int result = 0;
for (String name : myComponentStates.keySet()) {
result = 31 * result + name.hashCode();
result = 31 * result + JDOMUtil.getTreeHash(myComponentStates.get(name));
}
return result;
}
protected void clearHash() {
myHash = -1;
}
public Set<String> getChangedComponentNames(@NotNull StorageData storageData, @Nullable PathMacroSubstitutor substitutor) {
Set<String> bothStates = new THashSet<String>(myComponentStates.keySet());
bothStates.retainAll(storageData.myComponentStates.keySet());
Set<String> diffs = new THashSet<String>();
diffs.addAll(storageData.myComponentStates.keySet());
Set<String> diffs = new SmartHashSet<String>();
diffs.addAll(newStorageData.myComponentStates.keySet());
diffs.addAll(myComponentStates.keySet());
diffs.removeAll(bothStates);
for (String componentName : bothStates) {
final Element e1 = myComponentStates.get(componentName);
final Element e2 = storageData.myComponentStates.get(componentName);
// some configurations want to collapse path elements in writeExternal so make sure paths are expanded
if (substitutor != null) {
substitutor.expandPaths(e2);
Object oldState = myComponentStates.get(componentName);
Object newState = newStorageData.myComponentStates.get(componentName);
if (oldState instanceof Element) {
if (!JDOMUtil.areElementsEqual((Element)oldState, (Element)newState)) {
diffs.add(componentName);
}
}
if (!JDOMUtil.areElementsEqual(e1, e2)) {
else if (getNewByteIfDiffers(componentName, newState, (byte[])oldState) != null) {
diffs.add(componentName);
}
}
return diffs;
}
@@ -227,7 +374,7 @@ public class StorageData {
}
public boolean hasState(@NotNull String componentName) {
return myComponentStates.containsKey(componentName);
return myComponentStates.get(componentName) instanceof Element;
}
@NotNull
@@ -159,9 +159,4 @@ public class MockProjectStore implements IProjectStore {
public StateStorageManager getStateStorageManager() {
throw new UnsupportedOperationException("Method getStateStorageManager not implemented in " + getClass());
}
@Override
public boolean isSaving() {
return false;
}
}
@@ -18,13 +18,14 @@ package com.intellij.util;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* <p>Identifies a line separator:
* either Unix ({@code \n}), Windows (@{code \r\n}) or (possible not actual anymore) Classic Mac ({@code \r}).</p>
*
* <p/>
* <p>The intention is to use this class everywhere, where a line separator is needed, instead of just Strings.</p>
*
* @author Kirill Likhodedov
@@ -36,9 +37,11 @@ public enum LineSeparator {
private static final Logger LOG = Logger.getInstance(LineSeparator.class);
private final String mySeparatorString;
private final byte[] myBytes;
LineSeparator(@NotNull String separatorString) {
mySeparatorString = separatorString;
myBytes = separatorString.getBytes(CharsetToolkit.UTF8_CHARSET);
}
@NotNull
@@ -57,6 +60,11 @@ public enum LineSeparator {
return mySeparatorString;
}
@NotNull
public byte[] getSeparatorBytes() {
return myBytes;
}
public static boolean knownAndDifferent(@Nullable LineSeparator separator1, @Nullable LineSeparator separator2) {
return separator1 != null && separator2 != null && !separator1.equals(separator2);
}