IDEADEV-41473 (Invoking "Commit Changes" causes IDEA to freeze)
[fedora-idea.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / impl / projectlevelman / NewMappings.java
blobd41b2e17ce11314b99896d9cf2b1664dcfb81a00
1 /*
2 * Copyright 2000-2009 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.vcs.impl.projectlevelman;
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.util.Pair;
21 import com.intellij.openapi.util.Ref;
22 import com.intellij.openapi.util.io.FileUtil;
23 import com.intellij.openapi.util.text.StringUtil;
24 import com.intellij.openapi.vcs.*;
25 import com.intellij.openapi.vcs.impl.DefaultVcsRootPolicy;
26 import com.intellij.openapi.vfs.LocalFileSystem;
27 import com.intellij.openapi.vfs.VirtualFile;
28 import com.intellij.util.EventDispatcher;
29 import com.intellij.util.containers.Convertor;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
33 import java.util.*;
35 public class NewMappings {
36 private final static Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.impl.projectlevelman.NewMappings");
37 private final Object myLock;
39 // vcs to mappings
40 private final Map<String, List<VcsDirectoryMapping>> myVcsToPaths;
41 private AbstractVcs[] myActiveVcses;
42 private VcsDirectoryMapping[] mySortedMappings;
43 private final Map<VcsDirectoryMapping, LocalFileSystem.WatchRequest> myDirectoryMappingWatches;
45 private final DefaultVcsRootPolicy myDefaultVcsRootPolicy;
46 private final EventDispatcher<VcsListener> myEventDispatcher;
47 private final Project myProject;
49 public NewMappings(final Project project, final EventDispatcher<VcsListener> eventDispatcher) {
50 myProject = project;
51 myLock = new Object();
52 myVcsToPaths = new HashMap<String, List<VcsDirectoryMapping>>();
53 myDirectoryMappingWatches = new HashMap<VcsDirectoryMapping, LocalFileSystem.WatchRequest>();
54 myDefaultVcsRootPolicy = DefaultVcsRootPolicy.getInstance(project);
55 myActiveVcses = new AbstractVcs[0];
56 myEventDispatcher = eventDispatcher;
58 final ArrayList<VcsDirectoryMapping> listStr = new ArrayList<VcsDirectoryMapping>();
59 final VcsDirectoryMapping mapping = new VcsDirectoryMapping("", "");
60 listStr.add(mapping);
61 myVcsToPaths.put("", listStr);
62 mySortedMappings = new VcsDirectoryMapping[] {mapping};
65 public AbstractVcs[] getActiveVcses() {
66 synchronized (myLock) {
67 final AbstractVcs[] result = new AbstractVcs[myActiveVcses.length];
68 System.arraycopy(myActiveVcses, 0, result, 0, myActiveVcses.length);
69 return result;
73 @Modification
74 public void setMapping(final String path, final String activeVcsName) {
75 LOG.debug("setMapping path = '" + path + "' vcs = " + activeVcsName);
76 final VcsDirectoryMapping newMapping = new VcsDirectoryMapping(path, activeVcsName);
77 final LocalFileSystem.WatchRequest request = addWatchRequest(newMapping);
79 final Ref<Boolean> switched = new Ref<Boolean>(Boolean.FALSE);
80 keepActiveVcs(new Runnable() {
81 public void run() {
82 // sorted -> map. sorted mappings are NOT changed;
83 switched.set(trySwitchVcs(path, activeVcsName));
84 if (! switched.get().booleanValue()) {
85 final List<VcsDirectoryMapping> newList = listForVcsFromMap(newMapping.getVcs());
86 newList.add(newMapping);
87 sortedMappingsByMap();
89 if (request != null) {
90 myDirectoryMappingWatches.put(newMapping, request);
94 });
96 if (switched.get().booleanValue() && (request != null)) {
97 LocalFileSystem.getInstance().removeWatchedRoot(request);
100 mappingsChanged();
103 private void keepActiveVcs(final Runnable runnable) {
104 final MyVcsActivator activator;
105 synchronized (myLock) {
106 activator = new MyVcsActivator(new HashSet<String>(myVcsToPaths.keySet()));
107 runnable.run();
108 restoreActiveVcses();
110 activator.activate(myVcsToPaths.keySet(), AllVcses.getInstance(myProject));
113 private void restoreActiveVcses() {
114 synchronized (myLock) {
115 final Set<String> set = myVcsToPaths.keySet();
116 final List<AbstractVcs> list = new ArrayList<AbstractVcs>(set.size());
117 for (String s : set) {
118 if (s.trim().length() == 0) continue;
119 final AbstractVcs vcs = AllVcses.getInstance(myProject).getByName(s);
120 if (vcs != null) {
121 list.add(vcs);
124 myActiveVcses = list.toArray(new AbstractVcs[list.size()]);
128 public void mappingsChanged() {
129 myEventDispatcher.getMulticaster().directoryMappingChanged();
132 @Modification
133 public void setDirectoryMappings(final List<VcsDirectoryMapping> items) {
134 LOG.debug("setDirectoryMappings, size: " + items.size());
135 MySetMappingsPreProcessor setMappingsPreProcessor = new MySetMappingsPreProcessor(items);
136 setMappingsPreProcessor.invoke();
137 final List<VcsDirectoryMapping> itemsCopy = setMappingsPreProcessor.getItemsCopy();
138 final Map<VcsDirectoryMapping, LocalFileSystem.WatchRequest> requests = setMappingsPreProcessor.getRequests();
140 final Collection<LocalFileSystem.WatchRequest> toRemove = new ArrayList<LocalFileSystem.WatchRequest>();
142 keepActiveVcs(new Runnable() {
143 public void run() {
144 // a copy!
145 toRemove.addAll(myDirectoryMappingWatches.values());
146 myDirectoryMappingWatches.clear();
148 myVcsToPaths.clear();
149 for (VcsDirectoryMapping mapping : itemsCopy) {
150 listForVcsFromMap(mapping.getVcs()).add(mapping);
152 sortedMappingsByMap();
156 // do not remove what added and is active
157 toRemove.removeAll(requests.values());
158 // tracked by request object reference so OK to first add and then remove
159 LocalFileSystem.getInstance().removeWatchedRoots(toRemove);
161 mappingsChanged();
164 @Nullable
165 public VcsDirectoryMapping getMappingFor(VirtualFile file) {
166 if (file == null) return null;
167 if (! file.isInLocalFileSystem()) {
168 return null;
171 return getMappingFor(file, myDefaultVcsRootPolicy.getMatchContext(file));
174 @Nullable
175 public VcsDirectoryMapping getMappingFor(final VirtualFile file, final Object matchContext) {
176 // performance: calculate file path just once, rather than once per mapping
177 String path = file.getPath();
178 final String systemIndependPath = FileUtil.toSystemIndependentName((file.isDirectory() && (! path.endsWith("/"))) ? (path + "/") : path);
180 synchronized (myLock) {
181 for (int i = mySortedMappings.length - 1; i >= 0; -- i) {
182 final VcsDirectoryMapping mapping = mySortedMappings[i];
183 if (fileMatchesMapping(file, matchContext, systemIndependPath, mapping)) {
184 return mapping;
187 return null;
191 @Nullable
192 public String getVcsFor(@NotNull VirtualFile file) {
193 VcsDirectoryMapping mapping = getMappingFor(file);
194 if (mapping == null) {
195 return null;
197 return mapping.getVcs();
200 private boolean fileMatchesMapping(final VirtualFile file, final Object matchContext, final String systemIndependPath, final VcsDirectoryMapping mapping) {
201 if (mapping.getDirectory().length() == 0) {
202 return myDefaultVcsRootPolicy.matchesDefaultMapping(file, matchContext);
204 return FileUtil.startsWith(systemIndependPath, mapping.systemIndependentPath());
207 List<VirtualFile> getMappingsAsFilesUnderVcs(final AbstractVcs vcs) {
208 final List<VirtualFile> result = new ArrayList<VirtualFile>();
209 final String vcsName = vcs.getName();
211 final List<VcsDirectoryMapping> mappings;
212 synchronized (myLock) {
213 final List<VcsDirectoryMapping> vcsMappings = myVcsToPaths.get(vcsName);
214 if (vcsMappings == null) return result;
215 mappings = new ArrayList<VcsDirectoryMapping>(vcsMappings);
218 for (VcsDirectoryMapping mapping : mappings) {
219 if (mapping.isDefaultMapping()) {
220 // todo callback here; don't like it
221 myDefaultVcsRootPolicy.addDefaultVcsRoots(this, vcs, result);
222 } else {
223 final VirtualFile file = LocalFileSystem.getInstance().findFileByPath(mapping.getDirectory());
224 if (file != null) {
225 result.add(file);
229 return result;
232 @Modification
233 public void disposeMe() {
234 LOG.debug("dipose me");
235 clearImpl();
238 @Modification
239 public void clear() {
240 LOG.debug("clear");
241 clearImpl();
243 mappingsChanged();
246 private void clearImpl() {
247 // if vcses were not mapped, there's nothing to clear
248 if ((myActiveVcses == null) || (myActiveVcses.length == 0)) return;
250 final Collection<LocalFileSystem.WatchRequest> toRemove = new ArrayList<LocalFileSystem.WatchRequest>();
251 keepActiveVcs(new Runnable() {
252 public void run() {
253 // a copy!
254 toRemove.addAll(myDirectoryMappingWatches.values());
255 myDirectoryMappingWatches.clear();
257 myVcsToPaths.clear();
258 myActiveVcses = new AbstractVcs[0];
259 mySortedMappings = new VcsDirectoryMapping[0];
262 if (! toRemove.isEmpty()) {
263 LocalFileSystem.getInstance().removeWatchedRoots(toRemove);
267 public List<VcsDirectoryMapping> getDirectoryMappings() {
268 synchronized (myLock) {
269 return Arrays.asList(mySortedMappings);
273 public List<VcsDirectoryMapping> getDirectoryMappings(String vcsName) {
274 synchronized (myLock) {
275 final List<VcsDirectoryMapping> mappings = myVcsToPaths.get(vcsName);
276 return mappings == null ? new ArrayList<VcsDirectoryMapping>() : new ArrayList<VcsDirectoryMapping>(mappings);
280 public void cleanupMappings() {
281 final List<LocalFileSystem.WatchRequest> watchRequestList;
282 synchronized (myLock) {
283 watchRequestList = removeRedundantMappings();
285 LocalFileSystem.getInstance().removeWatchedRoots(watchRequestList);
288 @Nullable
289 public String haveDefaultMapping() {
290 synchronized (myLock) {
291 // empty mapping MUST be first
292 if (mySortedMappings.length == 0) return null;
293 return mySortedMappings[0].isDefaultMapping() ? mySortedMappings[0].getVcs() : null;
297 public boolean isEmpty() {
298 synchronized (myLock) {
299 return mySortedMappings.length == 0;
303 @Modification
304 public void removeDirectoryMapping(final VcsDirectoryMapping mapping) {
305 LOG.debug("remove mapping: " + mapping.getDirectory());
306 final Ref<LocalFileSystem.WatchRequest> request = new Ref<LocalFileSystem.WatchRequest>();
308 keepActiveVcs(new Runnable() {
309 public void run() {
310 if (removeVcsFromMap(mapping, mapping.getVcs())) {
311 sortedMappingsByMap();
312 request.set(myDirectoryMappingWatches.remove(mapping));
317 if (! request.isNull()) {
318 LocalFileSystem.getInstance().removeWatchedRoot(request.get());
321 mappingsChanged();
324 private class MyMappingsFilter extends AbstractFilterChildren<VcsDirectoryMapping> {
325 private final List<LocalFileSystem.WatchRequest> myRemovedRequests;
327 private MyMappingsFilter() {
328 myRemovedRequests = new ArrayList<LocalFileSystem.WatchRequest>();
331 protected void sortAscending(List<VcsDirectoryMapping> vcsDirectoryMappings) {
332 // todo ordering is actually here
333 Collections.sort(vcsDirectoryMappings, MyMappingsComparator.getInstance());
336 @Override
337 protected void onRemove(final VcsDirectoryMapping vcsDirectoryMapping) {
338 final LocalFileSystem.WatchRequest request = myDirectoryMappingWatches.remove(vcsDirectoryMapping);
339 if (request != null) {
340 myRemovedRequests.add(request);
344 protected boolean isAncestor(VcsDirectoryMapping parent, VcsDirectoryMapping child) {
345 if (! parent.getVcs().equals(child.getVcs())) return false;
347 final String parentPath = parent.systemIndependentPath();
348 final String fixedParentPath = (parentPath.endsWith("/")) ? parentPath : (parentPath + "/");
350 if (child.systemIndependentPath().length() < fixedParentPath.length()) {
351 return child.systemIndependentPath().equals(parentPath);
353 return child.systemIndependentPath().startsWith(fixedParentPath);
356 public List<LocalFileSystem.WatchRequest> getRemovedRequests() {
357 return myRemovedRequests;
361 // todo area for optimization
362 private List<LocalFileSystem.WatchRequest> removeRedundantMappings() {
363 final Set<Map.Entry<String, List<VcsDirectoryMapping>>> entries = myVcsToPaths.entrySet();
365 final LocalFileSystem lfs = LocalFileSystem.getInstance();
366 final AllVcsesI allVcses = AllVcses.getInstance(myProject);
368 final List<LocalFileSystem.WatchRequest> removedRequests = new LinkedList<LocalFileSystem.WatchRequest>();
370 for (Iterator<String> iterator = myVcsToPaths.keySet().iterator(); iterator.hasNext();) {
371 final String vcsName = iterator.next();
372 final List<VcsDirectoryMapping> mappings = myVcsToPaths.get(vcsName);
374 final List<Pair<VirtualFile, VcsDirectoryMapping>> objects = ObjectsConvertor.convert(mappings,
375 new Convertor<VcsDirectoryMapping, Pair<VirtualFile, VcsDirectoryMapping>>() {
376 public Pair<VirtualFile, VcsDirectoryMapping> convert(final VcsDirectoryMapping dm) {
377 VirtualFile vf = lfs.findFileByPath(dm.getDirectory());
378 if (vf == null) {
379 vf = lfs.refreshAndFindFileByPath(dm.getDirectory());
381 return vf == null ? null : new Pair<VirtualFile, VcsDirectoryMapping>(vf, dm);
383 }, ObjectsConvertor.NOT_NULL);
385 final List<Pair<VirtualFile, VcsDirectoryMapping>> filteredFiles;
386 // todo static
387 final Convertor<Pair<VirtualFile, VcsDirectoryMapping>, VirtualFile> fileConvertor =
388 new Convertor<Pair<VirtualFile, VcsDirectoryMapping>, VirtualFile>() {
389 public VirtualFile convert(Pair<VirtualFile, VcsDirectoryMapping> o) {
390 return o.getFirst();
393 if (StringUtil.isEmptyOrSpaces(vcsName)) {
394 filteredFiles = AbstractVcs.filterUniqueRootsDefault(objects, fileConvertor);
395 } else {
396 final AbstractVcs vcs = allVcses.getByName(vcsName);
397 filteredFiles = vcs.filterUniqueRoots(objects, fileConvertor);
400 final List<VcsDirectoryMapping> filteredMappings =
401 ObjectsConvertor.convert(filteredFiles, new Convertor<Pair<VirtualFile, VcsDirectoryMapping>, VcsDirectoryMapping>() {
402 public VcsDirectoryMapping convert(final Pair<VirtualFile, VcsDirectoryMapping> o) {
403 return o.getSecond();
407 // to calculate what had been removed
408 mappings.removeAll(filteredMappings);
409 for (VcsDirectoryMapping mapping : mappings) {
410 removedRequests.add(myDirectoryMappingWatches.remove(mapping));
413 if (filteredMappings.isEmpty()) {
414 iterator.remove();
415 } else {
416 mappings.clear();
417 mappings.addAll(filteredMappings);
421 sortedMappingsByMap();
422 return removedRequests;
425 private boolean trySwitchVcs(final String path, final String activeVcsName) {
426 final String fixedPath = FileUtil.toSystemIndependentName(path);
427 for (VcsDirectoryMapping mapping : mySortedMappings) {
428 if (mapping.systemIndependentPath().equals(fixedPath)) {
429 final String oldVcs = mapping.getVcs();
430 if (! oldVcs.equals(activeVcsName)) {
431 migrateVcs(activeVcsName, mapping, oldVcs);
433 return true;
436 return false;
439 private void sortedMappingsByMap() {
440 final List<VcsDirectoryMapping> list = new ArrayList<VcsDirectoryMapping>();
441 for (List<VcsDirectoryMapping> mappingList : myVcsToPaths.values()) {
442 list.addAll(mappingList);
444 mySortedMappings = list.toArray(new VcsDirectoryMapping[list.size()]);
445 Arrays.sort(mySortedMappings, MyMappingsComparator.getInstance());
448 private void migrateVcs(String activeVcsName, VcsDirectoryMapping mapping, String oldVcs) {
449 mapping.setVcs(activeVcsName);
451 removeVcsFromMap(mapping, oldVcs);
453 final List<VcsDirectoryMapping> newList = listForVcsFromMap(activeVcsName);
454 newList.add(mapping);
457 private boolean removeVcsFromMap(VcsDirectoryMapping mapping, String oldVcs) {
458 final List<VcsDirectoryMapping> oldList = myVcsToPaths.get(oldVcs);
459 if (oldList == null) return false;
461 final boolean result = oldList.remove(mapping);
462 if (oldList.isEmpty()) {
463 myVcsToPaths.remove(oldVcs);
465 return result;
468 // todo don't like it
469 private List<VcsDirectoryMapping> listForVcsFromMap(String activeVcsName) {
470 List<VcsDirectoryMapping> newList = myVcsToPaths.get(activeVcsName);
471 if (newList == null) {
472 newList = new ArrayList<VcsDirectoryMapping>();
473 myVcsToPaths.put(activeVcsName, newList);
475 return newList;
478 @Nullable
479 private static LocalFileSystem.WatchRequest addWatchRequest(final VcsDirectoryMapping mapping) {
480 if (! mapping.isDefaultMapping()) {
481 return LocalFileSystem.getInstance().addRootToWatch(mapping.getDirectory(), true);
483 return null;
486 private static class MyMappingsComparator implements Comparator<VcsDirectoryMapping> {
487 private static final MyMappingsComparator ourInstance = new MyMappingsComparator();
489 public static MyMappingsComparator getInstance() {
490 return ourInstance;
493 public int compare(VcsDirectoryMapping m1, VcsDirectoryMapping m2) {
494 return m1.getDirectory().compareTo(m2.getDirectory());
498 private static class MyVcsActivator {
499 private final Set<String> myOld;
501 public MyVcsActivator(final Set<String> old) {
502 myOld = old;
505 public void activate(final Set<String> newOne, final AllVcsesI vcsesI) {
506 final Set<String> toAdd = notInBottom(newOne, myOld);
507 final Set<String> toRemove = notInBottom(myOld, newOne);
508 if (toAdd != null) {
509 for (String s : toAdd) {
510 final AbstractVcs vcs = vcsesI.getByName(s);
511 if (vcs != null) {
512 try {
513 vcs.doActivate();
515 catch (VcsException e) {
516 // actually is not thrown (AbstractVcs#actualActivate())
518 } else {
519 LOG.info("Error: activating non existing vcs: " + s);
523 if (toRemove != null) {
524 for (String s : toRemove) {
525 final AbstractVcs vcs = vcsesI.getByName(s);
526 if (vcs != null) {
527 try {
528 vcs.doDeactivate();
530 catch (VcsException e) {
531 // actually is not thrown (AbstractVcs#actualDeactivate())
533 } else {
534 LOG.info("Error: removing non existing vcs: " + s);
540 @Nullable
541 private Set<String> notInBottom(final Set<String> top, final Set<String> bottom) {
542 Set<String> notInBottom = null;
543 for (String topItem : top) {
544 // omit empty vcs: not a vcs
545 if (topItem.trim().length() == 0) continue;
547 if (! bottom.contains(topItem)) {
548 if (notInBottom == null) {
549 notInBottom = new HashSet<String>();
551 notInBottom.add(topItem);
554 return notInBottom;
558 public boolean haveActiveVcs(final String name) {
559 synchronized (myLock) {
560 return myVcsToPaths.containsKey(name);
564 @Modification
565 public void beingUnregistered(final String name) {
566 synchronized (myLock) {
567 keepActiveVcs(new Runnable() {
568 public void run() {
569 final List<VcsDirectoryMapping> removed = myVcsToPaths.remove(name);
570 sortedMappingsByMap();
575 mappingsChanged();
578 private static class MySetMappingsPreProcessor {
579 private List<VcsDirectoryMapping> myItems;
580 private List<VcsDirectoryMapping> myItemsCopy;
581 private Map<VcsDirectoryMapping, LocalFileSystem.WatchRequest> myRequests;
583 public MySetMappingsPreProcessor(final List<VcsDirectoryMapping> items) {
584 myItems = items;
587 public List<VcsDirectoryMapping> getItemsCopy() {
588 return myItemsCopy;
591 public Map<VcsDirectoryMapping, LocalFileSystem.WatchRequest> getRequests() {
592 return myRequests;
595 public void invoke() {
596 if (myItems.isEmpty()) {
597 myItemsCopy = Collections.singletonList(new VcsDirectoryMapping("", ""));
598 myRequests = Collections.emptyMap();
599 } else {
600 myRequests = new HashMap<VcsDirectoryMapping, LocalFileSystem.WatchRequest>();
602 for (VcsDirectoryMapping item : myItems) {
603 final LocalFileSystem.WatchRequest request = addWatchRequest(item);
604 if (request != null) {
605 myRequests.put(item, request);
608 myItemsCopy = myItems;
613 private @interface Modification {