Moved "ThreadLocalDefendedInvoker" logic to "SvnFileUrlMappingImpl"
[idea/community.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / SvnFileUrlMappingImpl.java
1 /*
2  * Copyright 2000-2016 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 org.jetbrains.idea.svn;
17
18 import com.intellij.lifecycle.PeriodicalTasksCloser;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.components.*;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.progress.ProcessCanceledException;
23 import com.intellij.openapi.project.DumbAwareRunnable;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.util.io.FileUtil;
26 import com.intellij.openapi.vcs.ProjectLevelVcsManager;
27 import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl;
28 import com.intellij.openapi.vcs.impl.VcsInitObject;
29 import com.intellij.openapi.vfs.LocalFileSystem;
30 import com.intellij.openapi.vfs.VirtualFile;
31 import com.intellij.util.containers.ContainerUtil;
32 import com.intellij.util.messages.MessageBus;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35 import org.jetbrains.idea.svn.info.Info;
36 import org.tmatesoft.svn.core.SVNException;
37 import org.tmatesoft.svn.core.SVNURL;
38 import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
39
40 import java.io.File;
41 import java.util.List;
42 import java.util.Set;
43
44 @State(name = "SvnFileUrlMappingImpl", storages = @Storage(StoragePathMacros.WORKSPACE_FILE))
45 public class SvnFileUrlMappingImpl implements SvnFileUrlMapping, PersistentStateComponent<SvnMappingSavedPart>, ProjectComponent {
46   private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnFileUrlMappingImpl");
47
48   private final SvnCompatibilityChecker myChecker;
49   private final Object myMonitor = new Object();
50   // strictly: what real roots are under what vcs mappings
51   private final SvnMapping myMapping;
52   // grouped; if there are several mappings one under another, will return the upmost
53   private final SvnMapping myMoreRealMapping;
54   private final List<RootUrlInfo> myErrorRoots;
55   @NotNull private final MyRootsHelper myRootsHelper;
56   private final Project myProject;
57   private final NestedCopiesHolder myNestedCopiesHolder;
58   private boolean myInitialized;
59   private boolean myInitedReloaded;
60
61   private static class MyRootsHelper {
62     @NotNull private final static ThreadLocal<Boolean> ourInProgress = ThreadLocal.withInitial(() -> Boolean.FALSE);
63     @NotNull private final Project myProject;
64     @NotNull private final ProjectLevelVcsManager myVcsManager;
65
66     private MyRootsHelper(@NotNull Project project, @NotNull ProjectLevelVcsManager vcsManager) {
67       myProject = project;
68       myVcsManager = vcsManager;
69     }
70
71     @NotNull
72     public VirtualFile[] execute() {
73       try {
74         ourInProgress.set(Boolean.TRUE);
75         return myVcsManager.getRootsUnderVcs(SvnVcs.getInstance(myProject));
76       }
77       finally {
78         ourInProgress.set(Boolean.FALSE);
79       }
80     }
81
82     public static boolean isInProgress() {
83       return ourInProgress.get();
84     }
85   }
86
87   public static SvnFileUrlMappingImpl getInstance(final Project project) {
88     return PeriodicalTasksCloser.getInstance().safeGetComponent(project, SvnFileUrlMappingImpl.class);
89   }
90
91   @SuppressWarnings("UnusedDeclaration")
92   private SvnFileUrlMappingImpl(final Project project, final ProjectLevelVcsManager vcsManager) {
93     myProject = project;
94     myMapping = new SvnMapping();
95     myMoreRealMapping = new SvnMapping();
96     myErrorRoots = ContainerUtil.newArrayList();
97     myRootsHelper = new MyRootsHelper(project, vcsManager);
98     myChecker = new SvnCompatibilityChecker(project);
99     myNestedCopiesHolder = new NestedCopiesHolder();
100   }
101
102   @Nullable
103   public SVNURL getUrlForFile(final File file) {
104     final RootUrlInfo rootUrlInfo = getWcRootForFilePath(file);
105     if (rootUrlInfo == null) {
106       return null;
107     }
108
109     final String absolutePath = file.getAbsolutePath();
110     final String rootAbsPath = rootUrlInfo.getIoFile().getAbsolutePath();
111     if (absolutePath.length() < rootAbsPath.length()) {
112       // remove last separator from etalon name
113       if (absolutePath.equals(rootAbsPath.substring(0, rootAbsPath.length() - 1))) {
114         return rootUrlInfo.getAbsoluteUrlAsUrl();
115       }
116       return null;
117     }
118     final String relativePath = absolutePath.substring(rootAbsPath.length());
119     try {
120       return rootUrlInfo.getAbsoluteUrlAsUrl().appendPath(FileUtil.toSystemIndependentName(relativePath), true);
121     }
122     catch (SVNException e) {
123       LOG.info(e);
124       return null;
125     }
126   }
127
128   @Nullable
129   public File getLocalPath(@NotNull String url) {
130     synchronized (myMonitor) {
131       final String rootUrl = getUrlRootForUrl(url);
132       if (rootUrl == null) {
133         return null;
134       }
135       final RootUrlInfo parentInfo = myMoreRealMapping.byUrl(rootUrl);
136       if (parentInfo == null) {
137         return null;
138       }
139
140       return new File(parentInfo.getIoFile(), url.substring(rootUrl.length()));
141     }
142   }
143
144   @Nullable
145   public RootUrlInfo getWcRootForFilePath(final File file) {
146     synchronized (myMonitor) {
147       final String root = getRootForPath(file);
148       if (root == null) {
149         return null;
150       }
151
152       return myMoreRealMapping.byFile(root);
153     }
154   }
155
156   public boolean rootsDiffer() {
157     synchronized (myMonitor) {
158       return myMapping.isRootsDifferFromSettings();
159     }
160   }
161
162   @Nullable
163   public RootUrlInfo getWcRootForUrl(final String url) {
164     synchronized (myMonitor) {
165       final String rootUrl = getUrlRootForUrl(url);
166       if (rootUrl == null) {
167         return null;
168       }
169
170       final RootUrlInfo result = myMoreRealMapping.byUrl(rootUrl);
171       if (result == null) {
172         LOG.info("Inconsistent maps for url:" + url + " found root url: " + rootUrl);
173         return null;
174       }
175       return result;
176     }
177   }
178
179   /**
180    * Returns real working copies roots - if there is <Project Root> -> Subversion setting,
181    * and there is one working copy, will return one root
182    */
183   public List<RootUrlInfo> getAllWcInfos() {
184     synchronized (myMonitor) {
185       // a copy is created inside
186       return myMoreRealMapping.getAllCopies();
187     }
188   }
189
190   @Override
191   public List<RootUrlInfo> getErrorRoots() {
192     synchronized (myMonitor) {
193       return ContainerUtil.newArrayList(myErrorRoots);
194     }
195   }
196
197   @NotNull
198   public List<VirtualFile> convertRoots(@NotNull List<VirtualFile> result) {
199     if (MyRootsHelper.isInProgress()) return ContainerUtil.newArrayList(result);
200
201     synchronized (myMonitor) {
202       final List<VirtualFile> cachedRoots = myMoreRealMapping.getUnderVcsRoots();
203       final List<VirtualFile> lonelyRoots = myMoreRealMapping.getLonelyRoots();
204       if (! lonelyRoots.isEmpty()) {
205         myChecker.reportNoRoots(lonelyRoots);
206       }
207
208       return ContainerUtil.newArrayList(cachedRoots.isEmpty() ? result : cachedRoots);
209     }
210   }
211
212   public void acceptNestedData(final Set<NestedCopyInfo> set) {
213     myNestedCopiesHolder.add(set);
214   }
215
216   private boolean init() {
217     synchronized (myMonitor) {
218       final boolean result = myInitialized;
219       myInitialized = true;
220       return result;
221     }
222   }
223
224   public void realRefresh(final Runnable afterRefreshCallback) {
225     if (myProject.isDisposed()) {
226       afterRefreshCallback.run();
227     }
228     else {
229       final SvnVcs vcs = SvnVcs.getInstance(myProject);
230       final VirtualFile[] roots = myRootsHelper.execute();
231       final SvnRootsDetector rootsDetector = new SvnRootsDetector(vcs, this, myNestedCopiesHolder);
232       // do not send additional request for nested copies when in init state
233       rootsDetector.detectCopyRoots(roots, init(), afterRefreshCallback);
234     }
235   }
236
237   public void applyDetectionResult(@NotNull SvnRootsDetector.Result result) {
238     new NewRootsApplier(result).apply();
239   }
240
241   private class NewRootsApplier {
242
243     @NotNull private final SvnRootsDetector.Result myResult;
244     @NotNull private final SvnMapping myNewMapping;
245     @NotNull private final SvnMapping myNewFilteredMapping;
246
247     private NewRootsApplier(@NotNull SvnRootsDetector.Result result) {
248       myResult = result;
249       myNewMapping = new SvnMapping();
250       myNewFilteredMapping = new SvnMapping();
251     }
252
253     public void apply() {
254       myNewMapping.addAll(myResult.getTopRoots());
255       myNewMapping.reportLonelyRoots(myResult.getLonelyRoots());
256       myNewFilteredMapping.addAll(new UniqueRootsFilter().filter(myResult.getTopRoots()));
257
258       runUpdateMappings();
259     }
260
261     private void runUpdateMappings() {
262       // TODO: Not clear so far why read action is used here - may be because of ROOTS_RELOADED message sent?
263       ApplicationManager.getApplication().runReadAction(new Runnable() {
264         @Override
265         public void run() {
266           if (myProject.isDisposed()) return;
267
268           boolean mappingsChanged = updateMappings();
269
270           notifyRootsReloaded(mappingsChanged);
271         }
272       });
273     }
274
275     private boolean updateMappings() {
276       boolean mappingsChanged;
277       synchronized (myMonitor) {
278         mappingsChanged = ! myMoreRealMapping.equals(myNewFilteredMapping);
279         mappingsChanged |= !myErrorRoots.equals(myResult.getErrorRoots());
280
281         myMapping.copyFrom(myNewMapping);
282         myMoreRealMapping.copyFrom(myNewFilteredMapping);
283         myErrorRoots.clear();
284         myErrorRoots.addAll(myResult.getErrorRoots());
285       }
286       return mappingsChanged;
287     }
288
289     private void notifyRootsReloaded(boolean mappingsChanged) {
290       final MessageBus bus = myProject.getMessageBus();
291       if (mappingsChanged || ! myInitedReloaded) {
292         myInitedReloaded = true;
293         // all listeners are asynchronous
294         bus.syncPublisher(SvnVcs.ROOTS_RELOADED).consume(true);
295         bus.syncPublisher(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED_IN_PLUGIN).directoryMappingChanged();
296       } else {
297         bus.syncPublisher(SvnVcs.ROOTS_RELOADED).consume(false);
298       }
299     }
300   }
301
302   @Nullable
303   public String getUrlRootForUrl(final String currentUrl) {
304     for (String url : myMoreRealMapping.getUrls()) {
305       if (SVNPathUtil.isAncestor(url, currentUrl)) {
306         return url;
307       }
308     }
309     return null;
310   }
311
312   @Nullable
313   public String getRootForPath(final File currentPath) {
314     String convertedPath = currentPath.getAbsolutePath();
315     convertedPath = (currentPath.isDirectory() && (! convertedPath.endsWith(File.separator))) ? convertedPath + File.separator :
316         convertedPath;
317     synchronized (myMonitor) {
318       return myMoreRealMapping.getRootForPath(convertedPath);
319     }
320   }
321
322   @NotNull
323   public VirtualFile[] getNotFilteredRoots() {
324     return myRootsHelper.execute();
325   }
326
327   public boolean isEmpty() {
328     synchronized (myMonitor) {
329       return myMapping.isEmpty();
330     }
331   }
332
333   public SvnMappingSavedPart getState() {
334     final SvnMappingSavedPart result = new SvnMappingSavedPart();
335
336     final SvnMapping mapping = new SvnMapping();
337     final SvnMapping realMapping = new SvnMapping();
338     synchronized (myMonitor) {
339       mapping.copyFrom(myMapping);
340       realMapping.copyFrom(myMoreRealMapping);
341     }
342
343     for (RootUrlInfo info : mapping.getAllCopies()) {
344       result.add(convert(info));
345     }
346     for (RootUrlInfo info : realMapping.getAllCopies()) {
347       result.addReal(convert(info));
348     }
349     return result;
350   }
351
352   private SvnCopyRootSimple convert(final RootUrlInfo info) {
353     final SvnCopyRootSimple copy = new SvnCopyRootSimple();
354     copy.myVcsRoot = FileUtil.toSystemDependentName(info.getRoot().getPath());
355     copy.myCopyRoot = info.getIoFile().getAbsolutePath();
356     return copy;
357   }
358
359   public void loadState(final SvnMappingSavedPart state) {
360     ((ProjectLevelVcsManagerImpl) ProjectLevelVcsManager.getInstance(myProject)).addInitializationRequest(
361       VcsInitObject.AFTER_COMMON, new DumbAwareRunnable() {
362         public void run() {
363           ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
364             @Override
365             public void run() {
366               final SvnMapping mapping = new SvnMapping();
367               final SvnMapping realMapping = new SvnMapping();
368               try {
369                 fillMapping(mapping, state.getMappingRoots());
370                 fillMapping(realMapping, state.getMoreRealMappingRoots());
371               } catch (ProcessCanceledException e) {
372                 throw e;
373               } catch (Throwable t) {
374                 LOG.info(t);
375                 return;
376               }
377
378               synchronized (myMonitor) {
379                 myMapping.copyFrom(mapping);
380                 myMoreRealMapping.copyFrom(realMapping);
381               }
382             }
383           });
384         }
385     });
386   }
387
388   private void fillMapping(final SvnMapping mapping, final List<SvnCopyRootSimple> list) {
389     final LocalFileSystem lfs = LocalFileSystem.getInstance();
390
391     for (SvnCopyRootSimple simple : list) {
392       final VirtualFile copyRoot = lfs.findFileByIoFile(new File(simple.myCopyRoot));
393       final VirtualFile vcsRoot = lfs.findFileByIoFile(new File(simple.myVcsRoot));
394
395       if (copyRoot == null || vcsRoot == null) continue;
396
397       final SvnVcs vcs = SvnVcs.getInstance(myProject);
398       final Info svnInfo = vcs.getInfo(copyRoot);
399       if ((svnInfo == null) || (svnInfo.getRepositoryRootURL() == null)) continue;
400
401       Node node = new Node(copyRoot, svnInfo.getURL(), svnInfo.getRepositoryRootURL());
402       final RootUrlInfo info = new RootUrlInfo(node, SvnFormatSelector.findRootAndGetFormat(svnInfo.getFile()), vcsRoot);
403
404       mapping.add(info);
405     }
406   }
407
408   public void projectOpened() {
409   }
410
411   public void projectClosed() {
412   }
413
414   @NotNull
415   public String getComponentName() {
416     return "SvnFileUrlMappingImpl";
417   }
418
419   public void initComponent() {
420   }
421
422   public void disposeComponent() {
423   }
424 }