2 * Copyright 2000-2016 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 org.jetbrains.idea.svn;
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.ThreadLocalDefendedInvoker;
28 import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl;
29 import com.intellij.openapi.vcs.impl.VcsInitObject;
30 import com.intellij.openapi.vfs.LocalFileSystem;
31 import com.intellij.openapi.vfs.VirtualFile;
32 import com.intellij.util.containers.ContainerUtil;
33 import com.intellij.util.messages.MessageBus;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36 import org.jetbrains.idea.svn.info.Info;
37 import org.tmatesoft.svn.core.SVNException;
38 import org.tmatesoft.svn.core.SVNURL;
39 import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
42 import java.util.List;
45 @State(name = "SvnFileUrlMappingImpl", storages = @Storage(StoragePathMacros.WORKSPACE_FILE))
46 public class SvnFileUrlMappingImpl implements SvnFileUrlMapping, PersistentStateComponent<SvnMappingSavedPart>, ProjectComponent {
47 private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.SvnFileUrlMappingImpl");
49 private final SvnCompatibilityChecker myChecker;
50 private final Object myMonitor = new Object();
51 // strictly: what real roots are under what vcs mappings
52 private final SvnMapping myMapping;
53 // grouped; if there are several mappings one under another, will return the upmost
54 private final SvnMapping myMoreRealMapping;
55 private final List<RootUrlInfo> myErrorRoots;
56 private final MyRootsHelper myHelper;
57 private final Project myProject;
58 private final NestedCopiesHolder myNestedCopiesHolder;
59 private boolean myInitialized;
60 private boolean myInitedReloaded;
62 private static class MyRootsHelper extends ThreadLocalDefendedInvoker<VirtualFile[]> {
63 private final ProjectLevelVcsManager myPlVcsManager;
65 private MyRootsHelper(final ProjectLevelVcsManager vcsManager) {
66 myPlVcsManager = vcsManager;
69 protected VirtualFile[] execute(Project project) {
70 return myPlVcsManager.getRootsUnderVcs(SvnVcs.getInstance(project));
74 public static SvnFileUrlMappingImpl getInstance(final Project project) {
75 return PeriodicalTasksCloser.getInstance().safeGetComponent(project, SvnFileUrlMappingImpl.class);
78 @SuppressWarnings("UnusedDeclaration")
79 private SvnFileUrlMappingImpl(final Project project, final ProjectLevelVcsManager vcsManager) {
81 myMapping = new SvnMapping();
82 myMoreRealMapping = new SvnMapping();
83 myErrorRoots = ContainerUtil.newArrayList();
84 myHelper = new MyRootsHelper(vcsManager);
85 myChecker = new SvnCompatibilityChecker(project);
86 myNestedCopiesHolder = new NestedCopiesHolder();
90 public SVNURL getUrlForFile(final File file) {
91 final RootUrlInfo rootUrlInfo = getWcRootForFilePath(file);
92 if (rootUrlInfo == null) {
96 final String absolutePath = file.getAbsolutePath();
97 final String rootAbsPath = rootUrlInfo.getIoFile().getAbsolutePath();
98 if (absolutePath.length() < rootAbsPath.length()) {
99 // remove last separator from etalon name
100 if (absolutePath.equals(rootAbsPath.substring(0, rootAbsPath.length() - 1))) {
101 return rootUrlInfo.getAbsoluteUrlAsUrl();
105 final String relativePath = absolutePath.substring(rootAbsPath.length());
107 return rootUrlInfo.getAbsoluteUrlAsUrl().appendPath(FileUtil.toSystemIndependentName(relativePath), true);
109 catch (SVNException e) {
116 public File getLocalPath(@NotNull String url) {
117 synchronized (myMonitor) {
118 final String rootUrl = getUrlRootForUrl(url);
119 if (rootUrl == null) {
122 final RootUrlInfo parentInfo = myMoreRealMapping.byUrl(rootUrl);
123 if (parentInfo == null) {
127 return new File(parentInfo.getIoFile(), url.substring(rootUrl.length()));
132 public RootUrlInfo getWcRootForFilePath(final File file) {
133 synchronized (myMonitor) {
134 final String root = getRootForPath(file);
139 return myMoreRealMapping.byFile(root);
143 public boolean rootsDiffer() {
144 synchronized (myMonitor) {
145 return myMapping.isRootsDifferFromSettings();
150 public RootUrlInfo getWcRootForUrl(final String url) {
151 synchronized (myMonitor) {
152 final String rootUrl = getUrlRootForUrl(url);
153 if (rootUrl == null) {
157 final RootUrlInfo result = myMoreRealMapping.byUrl(rootUrl);
158 if (result == null) {
159 LOG.info("Inconsistent maps for url:" + url + " found root url: " + rootUrl);
167 * Returns real working copies roots - if there is <Project Root> -> Subversion setting,
168 * and there is one working copy, will return one root
170 public List<RootUrlInfo> getAllWcInfos() {
171 synchronized (myMonitor) {
172 // a copy is created inside
173 return myMoreRealMapping.getAllCopies();
178 public List<RootUrlInfo> getErrorRoots() {
179 synchronized (myMonitor) {
180 return ContainerUtil.newArrayList(myErrorRoots);
185 public List<VirtualFile> convertRoots(@NotNull List<VirtualFile> result) {
186 if (ThreadLocalDefendedInvoker.isInside()) return ContainerUtil.newArrayList(result);
188 synchronized (myMonitor) {
189 final List<VirtualFile> cachedRoots = myMoreRealMapping.getUnderVcsRoots();
190 final List<VirtualFile> lonelyRoots = myMoreRealMapping.getLonelyRoots();
191 if (! lonelyRoots.isEmpty()) {
192 myChecker.reportNoRoots(lonelyRoots);
195 return ContainerUtil.newArrayList(cachedRoots.isEmpty() ? result : cachedRoots);
199 public void acceptNestedData(final Set<NestedCopyInfo> set) {
200 myNestedCopiesHolder.add(set);
203 private boolean init() {
204 synchronized (myMonitor) {
205 final boolean result = myInitialized;
206 myInitialized = true;
211 public void realRefresh(final Runnable afterRefreshCallback) {
212 if (myProject.isDisposed()) {
213 afterRefreshCallback.run();
216 final SvnVcs vcs = SvnVcs.getInstance(myProject);
217 final VirtualFile[] roots = myHelper.executeDefended(myProject);
218 final SvnRootsDetector rootsDetector = new SvnRootsDetector(vcs, this, myNestedCopiesHolder);
219 // do not send additional request for nested copies when in init state
220 rootsDetector.detectCopyRoots(roots, init(), afterRefreshCallback);
224 public void applyDetectionResult(@NotNull SvnRootsDetector.Result result) {
225 new NewRootsApplier(result).apply();
228 private class NewRootsApplier {
230 @NotNull private final SvnRootsDetector.Result myResult;
231 @NotNull private final SvnMapping myNewMapping;
232 @NotNull private final SvnMapping myNewFilteredMapping;
234 private NewRootsApplier(@NotNull SvnRootsDetector.Result result) {
236 myNewMapping = new SvnMapping();
237 myNewFilteredMapping = new SvnMapping();
240 public void apply() {
241 myNewMapping.addAll(myResult.getTopRoots());
242 myNewMapping.reportLonelyRoots(myResult.getLonelyRoots());
243 myNewFilteredMapping.addAll(new UniqueRootsFilter().filter(myResult.getTopRoots()));
248 private void runUpdateMappings() {
249 // TODO: Not clear so far why read action is used here - may be because of ROOTS_RELOADED message sent?
250 ApplicationManager.getApplication().runReadAction(new Runnable() {
253 if (myProject.isDisposed()) return;
255 boolean mappingsChanged = updateMappings();
257 notifyRootsReloaded(mappingsChanged);
262 private boolean updateMappings() {
263 boolean mappingsChanged;
264 synchronized (myMonitor) {
265 mappingsChanged = ! myMoreRealMapping.equals(myNewFilteredMapping);
266 mappingsChanged |= !myErrorRoots.equals(myResult.getErrorRoots());
268 myMapping.copyFrom(myNewMapping);
269 myMoreRealMapping.copyFrom(myNewFilteredMapping);
270 myErrorRoots.clear();
271 myErrorRoots.addAll(myResult.getErrorRoots());
273 return mappingsChanged;
276 private void notifyRootsReloaded(boolean mappingsChanged) {
277 final MessageBus bus = myProject.getMessageBus();
278 if (mappingsChanged || ! myInitedReloaded) {
279 myInitedReloaded = true;
280 // all listeners are asynchronous
281 bus.syncPublisher(SvnVcs.ROOTS_RELOADED).consume(true);
282 bus.syncPublisher(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED_IN_PLUGIN).directoryMappingChanged();
284 bus.syncPublisher(SvnVcs.ROOTS_RELOADED).consume(false);
290 public String getUrlRootForUrl(final String currentUrl) {
291 for (String url : myMoreRealMapping.getUrls()) {
292 if (SVNPathUtil.isAncestor(url, currentUrl)) {
300 public String getRootForPath(final File currentPath) {
301 String convertedPath = currentPath.getAbsolutePath();
302 convertedPath = (currentPath.isDirectory() && (! convertedPath.endsWith(File.separator))) ? convertedPath + File.separator :
304 synchronized (myMonitor) {
305 return myMoreRealMapping.getRootForPath(convertedPath);
309 public VirtualFile[] getNotFilteredRoots() {
310 return myHelper.executeDefended(myProject);
313 public boolean isEmpty() {
314 synchronized (myMonitor) {
315 return myMapping.isEmpty();
319 public SvnMappingSavedPart getState() {
320 final SvnMappingSavedPart result = new SvnMappingSavedPart();
322 final SvnMapping mapping = new SvnMapping();
323 final SvnMapping realMapping = new SvnMapping();
324 synchronized (myMonitor) {
325 mapping.copyFrom(myMapping);
326 realMapping.copyFrom(myMoreRealMapping);
329 for (RootUrlInfo info : mapping.getAllCopies()) {
330 result.add(convert(info));
332 for (RootUrlInfo info : realMapping.getAllCopies()) {
333 result.addReal(convert(info));
338 private SvnCopyRootSimple convert(final RootUrlInfo info) {
339 final SvnCopyRootSimple copy = new SvnCopyRootSimple();
340 copy.myVcsRoot = FileUtil.toSystemDependentName(info.getRoot().getPath());
341 copy.myCopyRoot = info.getIoFile().getAbsolutePath();
345 public void loadState(final SvnMappingSavedPart state) {
346 ((ProjectLevelVcsManagerImpl) ProjectLevelVcsManager.getInstance(myProject)).addInitializationRequest(
347 VcsInitObject.AFTER_COMMON, new DumbAwareRunnable() {
349 ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
352 final SvnMapping mapping = new SvnMapping();
353 final SvnMapping realMapping = new SvnMapping();
355 fillMapping(mapping, state.getMappingRoots());
356 fillMapping(realMapping, state.getMoreRealMappingRoots());
357 } catch (ProcessCanceledException e) {
359 } catch (Throwable t) {
364 synchronized (myMonitor) {
365 myMapping.copyFrom(mapping);
366 myMoreRealMapping.copyFrom(realMapping);
374 private void fillMapping(final SvnMapping mapping, final List<SvnCopyRootSimple> list) {
375 final LocalFileSystem lfs = LocalFileSystem.getInstance();
377 for (SvnCopyRootSimple simple : list) {
378 final VirtualFile copyRoot = lfs.findFileByIoFile(new File(simple.myCopyRoot));
379 final VirtualFile vcsRoot = lfs.findFileByIoFile(new File(simple.myVcsRoot));
381 if (copyRoot == null || vcsRoot == null) continue;
383 final SvnVcs vcs = SvnVcs.getInstance(myProject);
384 final Info svnInfo = vcs.getInfo(copyRoot);
385 if ((svnInfo == null) || (svnInfo.getRepositoryRootURL() == null)) continue;
387 Node node = new Node(copyRoot, svnInfo.getURL(), svnInfo.getRepositoryRootURL());
388 final RootUrlInfo info = new RootUrlInfo(node, SvnFormatSelector.findRootAndGetFormat(svnInfo.getFile()), vcsRoot);
394 public void projectOpened() {
397 public void projectClosed() {
401 public String getComponentName() {
402 return "SvnFileUrlMappingImpl";
405 public void initComponent() {
408 public void disposeComponent() {