fix dispose for libraries in project structure + directory-based storage fix: set...
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / components / impl / stores / DirectoryBasedStorage.java
bloba0114ac7bddc8b58233bf578425193bbd01cc68a
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.components.impl.stores;
18 import com.intellij.application.options.PathMacrosCollector;
19 import com.intellij.openapi.Disposable;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.components.StateSplitter;
22 import com.intellij.openapi.components.StateStorage;
23 import com.intellij.openapi.components.Storage;
24 import com.intellij.openapi.components.TrackingPathMacroSubstitutor;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.fileTypes.FileTypeManager;
27 import com.intellij.openapi.util.Disposer;
28 import com.intellij.openapi.util.JDOMUtil;
29 import com.intellij.openapi.util.Pair;
30 import com.intellij.openapi.util.WriteExternalException;
31 import com.intellij.openapi.util.text.StringUtil;
32 import com.intellij.openapi.vfs.LocalFileSystem;
33 import com.intellij.openapi.vfs.VirtualFile;
34 import com.intellij.openapi.vfs.VirtualFileAdapter;
35 import com.intellij.openapi.vfs.VirtualFileEvent;
36 import com.intellij.openapi.vfs.tracker.VirtualFileTracker;
37 import com.intellij.util.PairConsumer;
38 import static com.intellij.util.io.fs.FileSystem.FILE_SYSTEM;
39 import com.intellij.util.io.fs.IFile;
40 import com.intellij.util.messages.MessageBus;
41 import gnu.trove.THashMap;
42 import org.jdom.Document;
43 import org.jdom.Element;
44 import org.jdom.JDOMException;
45 import org.jetbrains.annotations.NonNls;
46 import org.jetbrains.annotations.NotNull;
47 import org.jetbrains.annotations.Nullable;
48 import org.picocontainer.PicoContainer;
50 import java.io.File;
51 import java.io.IOException;
52 import java.util.*;
54 //todo: support missing plugins
56 //todo: support storage data
57 public class DirectoryBasedStorage implements StateStorage, Disposable {
58 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.components.impl.stores.DirectoryBasedStorage");
60 private final TrackingPathMacroSubstitutor myPathMacroSubstitutor;
61 private final IFile myDir;
62 private final StateSplitter mySplitter;
64 private Object mySession;
65 private MyStorageData myStorageData = null;
66 @NonNls private static final String COMPONENT = "component";
67 @NonNls private static final String NAME = "name";
69 private static final IFile[] EMPTY_FILES = new IFile[0];
71 private final FileTypeManager myFileTypeManager;
73 public DirectoryBasedStorage(final TrackingPathMacroSubstitutor pathMacroSubstitutor,
74 final String dir,
75 final StateSplitter splitter,
76 Disposable parentDisposable,
77 final PicoContainer picoContainer) {
78 assert dir.indexOf("$") < 0;
79 myPathMacroSubstitutor = pathMacroSubstitutor;
80 myDir = FILE_SYSTEM.createFile(dir);
81 mySplitter = splitter;
82 Disposer.register(parentDisposable, this);
84 VirtualFileTracker virtualFileTracker = (VirtualFileTracker)picoContainer.getComponentInstanceOfType(VirtualFileTracker.class);
85 MessageBus messageBus = (MessageBus)picoContainer.getComponentInstanceOfType(MessageBus.class);
88 if (virtualFileTracker != null && messageBus != null) {
89 final String path = myDir.getAbsolutePath();
90 final String fileUrl = LocalFileSystem.PROTOCOL_PREFIX + path.replace(File.separatorChar, '/');
93 final Listener listener = messageBus.syncPublisher(STORAGE_TOPIC);
94 virtualFileTracker.addTracker(fileUrl, new VirtualFileAdapter() {
95 public void contentsChanged(final VirtualFileEvent event) {
96 if (!StringUtil.endsWithIgnoreCase(event.getFile().getName(), ".xml")) return;
97 listener.storageFileChanged(event, DirectoryBasedStorage.this);
100 public void fileDeleted(final VirtualFileEvent event) {
101 if (!StringUtil.endsWithIgnoreCase(event.getFile().getName(), ".xml")) return;
102 listener.storageFileChanged(event, DirectoryBasedStorage.this);
105 public void fileCreated(final VirtualFileEvent event) {
106 if (!StringUtil.endsWithIgnoreCase(event.getFile().getName(), ".xml")) return;
107 listener.storageFileChanged(event, DirectoryBasedStorage.this);
109 }, false, this);
112 myFileTypeManager = FileTypeManager.getInstance();
115 @Nullable
116 public <T> T getState(final Object component, final String componentName, Class<T> stateClass, @Nullable T mergeInto)
117 throws StateStorageException {
118 if (myStorageData == null) myStorageData = loadState();
121 if (!myStorageData.containsComponent(componentName)) {
122 return DefaultStateSerializer.deserializeState(new Element(COMPONENT), stateClass, mergeInto);
125 final List<Element> subElements = new ArrayList<Element>();
126 myStorageData.processComponent(componentName, new PairConsumer<IFile, Element>() {
127 public void consume(final IFile iFile, final Element element) {
128 final List children = element.getChildren();
129 assert children.size() == 1;
130 final Element subElement = (Element)children.get(0);
131 subElement.detach();
132 subElements.add(subElement);
136 final Element state = new Element(COMPONENT);
137 mySplitter.mergeStatesInto(state, subElements.toArray(new Element[subElements.size()]));
138 myStorageData.removeComponent(componentName);
140 return DefaultStateSerializer.deserializeState(state, stateClass, mergeInto);
143 private MyStorageData loadState() throws StateStorageException {
144 MyStorageData storageData = new MyStorageData();
145 if (!myDir.exists()) {
146 return storageData;
148 try {
149 final IFile[] files = myDir.listFiles();
151 for (IFile file : files) {
152 if (!StringUtil.endsWithIgnoreCase(file.getName(), ".xml")) {
153 //do not load system files like .DS_Store on Mac
154 continue;
156 final Document document = JDOMUtil.loadDocument(file);
157 final Element element = document.getRootElement();
158 assert element.getName().equals(COMPONENT);
160 String componentName = element.getAttributeValue(NAME);
161 assert componentName != null;
163 if (myPathMacroSubstitutor != null) {
164 myPathMacroSubstitutor.expandPaths(element);
166 final Set<String> unknownMacros = PathMacrosCollector.getMacroNames(element);
167 myPathMacroSubstitutor.addUnknownMacros(componentName, unknownMacros);
170 storageData.put(componentName, file, element, true);
173 catch (IOException e) {
174 throw new StateStorageException(e);
176 catch (JDOMException e) {
177 throw new StateStorageException(e);
180 return storageData;
184 public boolean hasState(final Object component, final String componentName, final Class<?> aClass, final boolean reloadData) throws StateStorageException {
185 if (!myDir.exists()) return false;
186 return true;
189 @NotNull
190 public ExternalizationSession startExternalization() {
191 if (myStorageData == null) {
192 try {
193 myStorageData = loadState();
195 catch (StateStorageException e) {
196 LOG.error(e);
199 final ExternalizationSession session = new MyExternalizationSession(myPathMacroSubstitutor, myStorageData.clone());
201 mySession = session;
202 return session;
205 @NotNull
206 public SaveSession startSave(final ExternalizationSession externalizationSession) {
207 assert mySession == externalizationSession;
209 final MySaveSession session =
210 new MySaveSession(((MyExternalizationSession)externalizationSession).myStorageData, myPathMacroSubstitutor);
211 mySession = session;
212 return session;
215 public void finishSave(final SaveSession saveSession) {
216 try {
217 LOG.assertTrue(mySession == saveSession);
218 } finally {
219 mySession = null;
223 public void reload(final Set<String> changedComponents) throws StateStorageException {
224 myStorageData = loadState();
227 public void dispose() {
230 private class MySaveSession implements SaveSession {
231 private final MyStorageData myStorageData;
232 private final TrackingPathMacroSubstitutor myPathMacroSubstitutor;
234 private MySaveSession(final MyStorageData storageData, final TrackingPathMacroSubstitutor pathMacroSubstitutor) {
235 myStorageData = storageData;
236 myPathMacroSubstitutor = pathMacroSubstitutor;
239 public void save() throws StateStorageException {
240 assert mySession == this;
241 final Set<String> currentNames = new HashSet<String>();
243 IFile[] children = myDir.exists() ? myDir.listFiles() : EMPTY_FILES;
244 for (IFile child : children) {
245 final String fileName = child.getName();
246 if (!myFileTypeManager.isFileIgnored(fileName)) {
247 currentNames.add(fileName);
251 myStorageData.process(new StorageDataProcessor() {
252 public void process(final String componentName, final IFile file, final Element element) {
253 currentNames.remove(file.getName());
255 if (myPathMacroSubstitutor != null) {
256 myPathMacroSubstitutor.collapsePaths(element);
259 if (file.getTimeStamp() <= myStorageData.getLastTimeStamp()) {
260 if (!myDir.exists()) {
261 myDir.createParentDirs();
262 myDir.mkDir();
265 StorageUtil.save(file, element, MySaveSession.this);
266 myStorageData.updateLastTimestamp(file);
271 ApplicationManager.getApplication().runWriteAction(new Runnable() {
272 public void run() {
273 if (myDir.exists()) {
274 for (String name : currentNames) {
275 IFile child = myDir.getChild(name);
277 if (child.getTimeStamp() > myStorageData.getLastTimeStamp()) {
278 // do not touch new files during VC update (which aren't read yet)
279 // now got an opposite problem: file is recreated if was removed by VC during update.
280 return;
283 final VirtualFile virtualFile = StorageUtil.getVirtualFile(child);
284 if (virtualFile != null) {
285 try {
286 LOG.debug("Removing configuration file: " + virtualFile.getPresentableUrl());
287 virtualFile.delete(MySaveSession.this);
289 catch (IOException e) {
290 LOG.error(e);
298 myStorageData.clear();
301 @Nullable
302 public Set<String> analyzeExternalChanges(final Set<Pair<VirtualFile, StateStorage>> changedFiles) {
303 boolean containsSelf = false;
305 for (Pair<VirtualFile, StateStorage> pair : changedFiles) {
306 if (pair.second == DirectoryBasedStorage.this) {
307 VirtualFile file = pair.first;
308 if ("xml".equalsIgnoreCase(file.getExtension())) {
309 containsSelf = true;
310 break;
315 if (!containsSelf) return Collections.emptySet();
317 if (myStorageData.getComponentNames().size() == 0) {
318 // no state yet, so try to initialize it now
319 final MyStorageData storageData = loadState();
320 return new HashSet<String>(storageData.getComponentNames());
323 return new HashSet<String>(myStorageData.getComponentNames());
326 public Collection<IFile> getStorageFilesToSave() throws StateStorageException {
327 assert mySession == this;
329 if (!myDir.exists()) return getAllStorageFiles();
330 assert myDir.isDirectory();
332 final List<IFile> filesToSave = new ArrayList<IFile>();
334 IFile[] children = myDir.listFiles();
335 final Set<String> currentChildNames = new HashSet<String>();
336 for (IFile child : children) {
337 if (!myFileTypeManager.isFileIgnored(child.getName())) currentChildNames.add(child.getName());
340 myStorageData.process(new StorageDataProcessor() {
341 public void process(final String componentName, final IFile file, final Element element) {
342 if (currentChildNames.contains(file.getName())) {
343 currentChildNames.remove(file.getName());
345 if (myPathMacroSubstitutor != null) {
346 myPathMacroSubstitutor.collapsePaths(element);
349 if (!StorageUtil.contentEquals(element, file)) {
350 filesToSave.add(file);
357 for (String childName : currentChildNames) {
358 final IFile child = myDir.getChild(childName);
359 filesToSave.add(child);
362 return filesToSave;
365 public List<IFile> getAllStorageFiles() {
366 return new ArrayList<IFile>(myStorageData.getAllStorageFiles().keySet());
370 private interface StorageDataProcessor {
371 void process(String componentName, IFile file, Element element);
374 private static class MyStorageData {
375 private Map<String, Map<IFile, Element>> myStates = new HashMap<String, Map<IFile, Element>>();
376 private long myLastTimestamp = 0;
377 private MyStorageData myOriginalData;
379 public Set<String> getComponentNames() {
380 return myStates.keySet();
383 public void put(final String componentName, final IFile file, final Element element, final boolean updateTimestamp) {
384 LOG.assertTrue(componentName != null, String.format("Component name should not be null for file: %s", file == null ? "NULL!" : file.getPath()));
386 Map<IFile, Element> stateMap = myStates.get(componentName);
387 if (stateMap == null) {
388 stateMap = new HashMap<IFile, Element>();
389 myStates.put(componentName, stateMap);
392 stateMap.put(file, element);
393 if (updateTimestamp) updateLastTimestamp(file);
396 public void updateLastTimestamp(final IFile file) {
397 myLastTimestamp = Math.max(myLastTimestamp, file.getTimeStamp());
398 if (myOriginalData != null) myOriginalData.myLastTimestamp = myLastTimestamp;
401 public long getLastTimeStamp() {
402 return myLastTimestamp;
405 public Map<IFile, Long> getAllStorageFiles() {
406 final Map<IFile, Long> allStorageFiles = new THashMap<IFile, Long>();
407 process(new StorageDataProcessor() {
408 public void process(final String componentName, final IFile file, final Element element) {
409 allStorageFiles.put(file, file.getTimeStamp());
413 return allStorageFiles;
416 public void processComponent(@NotNull final String componentName, @NotNull final PairConsumer<IFile, Element> consumer) {
417 final Map<IFile, Element> map = myStates.get(componentName);
418 if (map != null) {
419 for (IFile file : map.keySet()) {
420 consumer.consume(file, map.get(file));
425 public void process(@NotNull final StorageDataProcessor processor) {
426 for (final String componentName : myStates.keySet()) {
427 processComponent(componentName, new PairConsumer<IFile, Element>() {
428 public void consume(final IFile iFile, final Element element) {
429 processor.process(componentName, iFile, element);
435 protected MyStorageData clone() {
436 final MyStorageData result = new MyStorageData();
437 result.myStates = new HashMap<String, Map<IFile, Element>>(myStates);
438 result.myLastTimestamp = myLastTimestamp;
439 result.myOriginalData = this;
440 return result;
443 public void clear() {
444 myStates.clear();
445 myOriginalData = null;
448 public boolean containsComponent(final String componentName) {
449 return myStates.get(componentName) != null;
452 public void removeComponent(final String componentName) {
453 myStates.remove(componentName);
457 private class MyExternalizationSession implements ExternalizationSession {
458 private final MyStorageData myStorageData;
460 private MyExternalizationSession(final TrackingPathMacroSubstitutor pathMacroSubstitutor, final MyStorageData storageData) {
461 myStorageData = storageData;
464 public void setState(final Object component, final String componentName, final Object state, final Storage storageSpec)
465 throws StateStorageException {
466 assert mySession == this;
467 setState(componentName, state, storageSpec);
470 private void setState(final String componentName, Object state, final Storage storageSpec) throws StateStorageException {
471 try {
472 final Element element = DefaultStateSerializer.serializeState(state, storageSpec);
474 final List<Pair<Element, String>> states = mySplitter.splitState(element);
475 for (Pair<Element, String> pair : states) {
476 Element e = pair.first;
477 String name = pair.second;
479 Element statePart = new Element(COMPONENT);
480 statePart.setAttribute(NAME, componentName);
481 e.detach();
482 statePart.addContent(e);
484 myStorageData.put(componentName, myDir.getChild(name), statePart, false);
487 catch (WriteExternalException e) {
488 throw new StateStorageException(e);