2 * Copyright 2000-2014 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.vfs.ex.dummy;
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;
36 import java.io.IOException;
37 import java.util.Collection;
38 import java.util.List;
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");
46 private final String myProtocol;
48 private final BidirectionalMap<Project, String> myProject2Id = new BidirectionalMap<>();
50 private final FactoryMap<String, T> myCachedFiles = new ConcurrentFactoryMap<String, T>() {
52 protected T create(String key) {
53 if (ApplicationManager.getApplication().isUnitTestMode()) {
54 //noinspection TestOnlyProblems
58 return findFileByPathInner(key);
62 public T get(Object key) {
63 T file = super.get(key);
64 if (file != null && !file.isValid()) {
66 return super.get(key);
72 public DummyCachingFileSystem(String protocol) {
73 myProtocol = protocol;
75 final Application application = ApplicationManager.getApplication();
76 application.getMessageBus().connect(application).subscribe(ProjectManager.TOPIC, new ProjectManagerAdapter() {
78 public void projectOpened(final Project project) {
79 onProjectOpened(project);
87 public final String getProtocol() {
93 public final VirtualFile createRoot(String name) {
98 public final T findFileByPath(@NotNull String path) {
99 return myCachedFiles.get(path);
104 public String extractPresentableUrl(@NotNull String path) {
105 VirtualFile file = findFileByPath(path);
106 return file != null ? getPresentableUrl(file) : super.extractPresentableUrl(path);
109 protected String getPresentableUrl(@NotNull VirtualFile file) {
110 return file.getPresentableName();
113 protected abstract T findFileByPathInner(@NotNull String path);
115 protected void doRenameFile(VirtualFile vFile, String newName) {
116 throw new UnsupportedOperationException("not implemented");
120 public Project getProject(String projectId) {
121 List<Project> list = myProject2Id.getKeysByValue(projectId);
122 return list == null || list.size() > 1 ? null : list.get(0);
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()));
131 if (list.size() != 1) {
132 throw new AssertionError(projectId + " is mapped to several projects: " + list);
138 public Collection<T> getCachedFiles() {
139 return myCachedFiles.notNullValues();
142 public void onProjectClosed(Project project) {
143 myProject2Id.remove(project);
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() {
152 public void dispose() {
153 onProjectClosed(project);
158 String projectId = project.getLocationHash();
159 myProject2Id.put(project, projectId);
161 List<Project> projects = myProject2Id.getKeysByValue(projectId);
162 if (projects != null && projects.size() > 1) {
163 LOG.error("project " + projectId + " already registered: " + projects);
167 private void initProjectMap() {
168 for (Project project : ProjectManager.getInstance().getOpenProjects()) {
169 if (project.isOpen()) onProjectOpened(project);
173 protected void clearCache() {
177 protected void clearInvalidFiles() {
178 for (T t : myCachedFiles.notNullValues()) {
179 if (!t.isValid()) myCachedFiles.removeValue(t);
181 //noinspection StatementWithEmptyBody
182 while (myCachedFiles.removeValue(null)) ;
186 public void cleanup() {
187 myCachedFiles.clear();
188 myProject2Id.clear();
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);
196 doRenameFile(vFile, newName);
199 fileRenamed(vFile, requestor, oldName, newName);
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());
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);
214 protected static String escapeSlash(String name) {
215 return name == null ? "" : StringUtil.replace(name, "/", "&slash;");
218 protected static String unescapeSlash(String name) {
219 return name == null ? "" : StringUtil.replace(name, "&slash;", "/");