make project map concurrent
[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.ConcurrentFactoryMap;
29 import com.intellij.util.containers.ContainerUtil;
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.concurrent.ConcurrentMap;
38
39 /**
40  * @author gregsh
41  */
42 public abstract class DummyCachingFileSystem<T extends VirtualFile> extends DummyFileSystem {
43   private static final Logger LOG = Logger.getInstance(DummyCachingFileSystem.class);
44
45   private final String myProtocol;
46
47   private final ConcurrentMap<String, Project> myProjectMap = ContainerUtil.newConcurrentMap();
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(@Nullable String projectId) {
120     return myProjectMap.get(projectId);
121   }
122
123   @NotNull
124   public Project getProjectOrFail(String projectId) {
125     Project project = myProjectMap.get(projectId);
126     if (project == null) {
127       throw new AssertionError(String.format("'%s' project not found among %s", projectId,
128                                              ContainerUtil.newArrayList(myProjectMap.keySet())));
129     }
130     return project;
131   }
132
133   @NotNull
134   public Collection<T> getCachedFiles() {
135     return myCachedFiles.notNullValues();
136   }
137
138   @TestOnly
139   public void onProjectClosed(@NotNull Project project) {
140     onProjectClosedInner(project);
141   }
142
143   protected void onProjectClosedInner(@NotNull Project project) {
144     String projectId = project.getLocationHash();
145     Project mapped = myProjectMap.remove(projectId);
146     if (mapped == null) {
147       LOG.warn(String.format("'%s' project not mapped", projectId));
148     }
149     clearCache();
150   }
151
152   public void onProjectOpened(final Project project) {
153     // use Disposer instead of ProjectManagerListener#projectClosed() because Disposer.dispose(project)
154     // is called later and some cached files should stay valid till the last moment
155     Disposer.register(project, new Disposable() {
156       @Override
157       public void dispose() {
158         onProjectClosedInner(project);
159       }
160     });
161
162     clearCache();
163     String projectId = project.getLocationHash();
164     Project mapped = myProjectMap.put(projectId, project);
165
166     if (mapped != null) {
167       LOG.error(String.format("'%s' project rebound, was %s", projectId, mapped));
168     }
169   }
170
171   private void initProjectMap() {
172     for (Project project : ProjectManager.getInstance().getOpenProjects()) {
173       if (project.isOpen()) onProjectOpened(project);
174     }
175   }
176
177   protected void clearCache() {
178     clearInvalidFiles();
179   }
180
181   protected void clearInvalidFiles() {
182     for (T t : myCachedFiles.notNullValues()) {
183       if (!t.isValid()) myCachedFiles.removeValue(t);
184     }
185     //noinspection StatementWithEmptyBody
186     while (myCachedFiles.removeValue(null)) ;
187   }
188
189   @TestOnly
190   public void cleanup() {
191     myCachedFiles.clear();
192     myProjectMap.clear();
193   }
194
195   @Override
196   public void renameFile(Object requestor, @NotNull VirtualFile vFile, @NotNull String newName) throws IOException {
197     String oldName = vFile.getName();
198     beforeFileRename(vFile, requestor, oldName, newName);
199     try {
200       doRenameFile(vFile, newName);
201     }
202     finally {
203       fileRenamed(vFile, requestor, oldName, newName);
204     }
205   }
206
207   protected void beforeFileRename(@NotNull VirtualFile file, Object requestor, @NotNull String oldName, @NotNull String newName) {
208     fireBeforePropertyChange(requestor, file, VirtualFile.PROP_NAME, oldName, newName);
209     myCachedFiles.remove(file.getPath());
210   }
211
212   protected void fileRenamed(@NotNull VirtualFile file, Object requestor, String oldName, String newName) {
213     //noinspection unchecked
214     myCachedFiles.put(file.getPath(), (T)file);
215     firePropertyChanged(requestor, file, VirtualFile.PROP_NAME, oldName, newName);
216   }
217
218   protected static String escapeSlash(String name) {
219     return name == null ? "" : StringUtil.replace(name, "/", "&slash;");
220   }
221
222   protected static String unescapeSlash(String name) {
223     return name == null ? "" : StringUtil.replace(name, "&slash;", "/");
224   }
225 }