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