"AssertionError: Already disposed" when opening project in the same frame
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / vfs / ex / dummy / DummyCachingFileSystem.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.vfs.ex.dummy;
17
18 import com.intellij.openapi.Disposable;
19 import com.intellij.openapi.application.Application;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.project.ProjectManager;
24 import com.intellij.openapi.project.ProjectManagerAdapter;
25 import com.intellij.openapi.util.Disposer;
26 import com.intellij.openapi.util.text.StringUtil;
27 import com.intellij.openapi.vfs.VirtualFile;
28 import com.intellij.util.containers.BidirectionalMap;
29 import com.intellij.util.containers.ConcurrentFactoryMap;
30 import com.intellij.util.containers.FactoryMap;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
33 import org.jetbrains.annotations.TestOnly;
34
35 import java.io.IOException;
36 import java.util.Collection;
37 import java.util.List;
38
39 /**
40  * @author gregsh
41  */
42 public abstract class DummyCachingFileSystem<T extends VirtualFile> extends DummyFileSystem {
43   private static final Logger LOG = Logger.getInstance("com.intellij.openapi.vfs.ex.dummy.DummyCachingFileSystem");
44
45   private final String myProtocol;
46
47   private final BidirectionalMap<Project, String> myProject2Id = new BidirectionalMap<Project, String>();
48
49   private final FactoryMap<String, T> myCachedFiles = new ConcurrentFactoryMap<String, T>() {
50     @Override
51     protected T create(String key) {
52       if (ApplicationManager.getApplication().isUnitTestMode()) {
53         //noinspection TestOnlyProblems
54         cleanup();
55         initProjectMap();
56       }
57       return findFileByPathInner(key);
58     }
59
60     @Override
61     public T get(Object key) {
62       T file = super.get(key);
63       if (file != null && !file.isValid()) {
64         remove(key);
65         return super.get(key);
66       }
67       return file;
68     }
69   };
70
71   public DummyCachingFileSystem(String protocol) {
72     myProtocol = protocol;
73
74     final Application application = ApplicationManager.getApplication();
75     application.getMessageBus().connect(application).subscribe(ProjectManager.TOPIC, new ProjectManagerAdapter() {
76       @Override
77       public void projectOpened(final Project project) {
78         onProjectOpened(project);
79       }
80     });
81     initProjectMap();
82   }
83
84   @NotNull
85   @Override
86   public final String getProtocol() {
87     return myProtocol;
88   }
89
90   @Override
91   @Nullable
92   public final VirtualFile createRoot(String name) {
93     return null;
94   }
95
96   @Override
97   public final T findFileByPath(@NotNull String path) {
98     return myCachedFiles.get(path);
99   }
100
101   @Override
102   @NotNull
103   public String extractPresentableUrl(@NotNull String path) {
104     VirtualFile file = findFileByPath(path);
105     return file != null ? getPresentableUrl(file) : super.extractPresentableUrl(path);
106   }
107
108   protected String getPresentableUrl(@NotNull VirtualFile file) {
109     return file.getPresentableName();
110   }
111
112   protected abstract T findFileByPathInner(@NotNull String path);
113
114   protected void doRenameFile(VirtualFile vFile, String newName) {
115     throw new UnsupportedOperationException("not implemented");
116   }
117
118   @Nullable
119   public Project getProject(String projectId) {
120     List<Project> list = myProject2Id.getKeysByValue(projectId);
121     return list == null || list.size() > 1 ? null : list.get(0);
122   }
123
124   @NotNull
125   public Collection<T> getCachedFiles() {
126     return myCachedFiles.notNullValues();
127   }
128
129   public void onProjectClosed(Project project) {
130     myProject2Id.remove(project);
131     clearCache();
132   }
133
134   public void onProjectOpened(final Project project) {
135     // use Disposer instead of ProjectManagerListener#projectClosed() because Disposer.dispose(project)
136     // is called later and some cached files should stay valid till the last moment
137     Disposer.register(project, new Disposable() {
138       @Override
139       public void dispose() {
140         onProjectClosed(project);
141       }
142     });
143
144     clearCache();
145     String projectId = project.getLocationHash();
146     myProject2Id.put(project, projectId);
147
148     List<Project> projects = myProject2Id.getKeysByValue(projectId);
149     if (projects != null && projects.size() > 1) {
150       LOG.error("project " + projectId + " already registered: " + projects);
151     }
152   }
153
154   private void initProjectMap() {
155     for (Project project : ProjectManager.getInstance().getOpenProjects()) {
156       if (project.isOpen()) onProjectOpened(project);
157     }
158   }
159
160   protected void clearCache() {
161     clearInvalidFiles();
162   }
163
164   protected void clearInvalidFiles() {
165     for (T t : myCachedFiles.notNullValues()) {
166       if (!t.isValid()) myCachedFiles.removeValue(t);
167     }
168     //noinspection StatementWithEmptyBody
169     while (myCachedFiles.removeValue(null)) ;
170   }
171
172   @TestOnly
173   public void cleanup() {
174     myCachedFiles.clear();
175     myProject2Id.clear();
176   }
177
178   @Override
179   public void renameFile(Object requestor, @NotNull VirtualFile vFile, @NotNull String newName) throws IOException {
180     String oldName = vFile.getName();
181     beforeFileRename(vFile, requestor, oldName, newName);
182     try {
183       doRenameFile(vFile, newName);
184     }
185     finally {
186       fileRenamed(vFile, requestor, oldName, newName);
187     }
188   }
189
190   protected void beforeFileRename(@NotNull VirtualFile file, Object requestor, @NotNull String oldName, @NotNull String newName) {
191     fireBeforePropertyChange(requestor, file, VirtualFile.PROP_NAME, oldName, newName);
192     myCachedFiles.remove(file.getPath());
193   }
194
195   protected void fileRenamed(@NotNull VirtualFile file, Object requestor, String oldName, String newName) {
196     //noinspection unchecked
197     myCachedFiles.put(file.getPath(), (T)file);
198     firePropertyChanged(requestor, file, VirtualFile.PROP_NAME, oldName, newName);
199   }
200
201   protected static String escapeSlash(String name) {
202     return name == null ? "" : StringUtil.replace(name, "/", "&slash;");
203   }
204
205   protected static String unescapeSlash(String name) {
206     return name == null ? "" : StringUtil.replace(name, "&slash;", "/");
207   }
208 }