fix disappearing mappings
[fedora-idea.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / impl / projectlevelman / NewMappings.java
blob020c9a50f5b8b88bffd90fbe6576195708a3d2d1
1 package com.intellij.openapi.vcs.impl.projectlevelman;
3 import com.intellij.openapi.diagnostic.Logger;
4 import com.intellij.openapi.project.Project;
5 import com.intellij.openapi.util.Ref;
6 import com.intellij.openapi.util.io.FileUtil;
7 import com.intellij.openapi.vcs.*;
8 import com.intellij.openapi.vcs.impl.DefaultVcsRootPolicy;
9 import com.intellij.openapi.vfs.LocalFileSystem;
10 import com.intellij.openapi.vfs.VirtualFile;
11 import com.intellij.util.EventDispatcher;
12 import org.jetbrains.annotations.NotNull;
13 import org.jetbrains.annotations.Nullable;
15 import java.util.*;
17 public class NewMappings {
18 private final static Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.impl.projectlevelman.NewMappings");
19 private final Object myLock;
21 // vcs to mappings
22 private final Map<String, List<VcsDirectoryMapping>> myVcsToPaths;
23 private AbstractVcs[] myActiveVcses;
24 private VcsDirectoryMapping[] mySortedMappings;
25 private final Map<VcsDirectoryMapping, LocalFileSystem.WatchRequest> myDirectoryMappingWatches;
27 private final DefaultVcsRootPolicy myDefaultVcsRootPolicy;
28 private final EventDispatcher<VcsListener> myEventDispatcher;
29 private final Project myProject;
31 public NewMappings(final Project project, final EventDispatcher<VcsListener> eventDispatcher) {
32 myProject = project;
33 myLock = new Object();
34 myVcsToPaths = new HashMap<String, List<VcsDirectoryMapping>>();
35 myDirectoryMappingWatches = new HashMap<VcsDirectoryMapping, LocalFileSystem.WatchRequest>();
36 myDefaultVcsRootPolicy = DefaultVcsRootPolicy.getInstance(project);
37 myActiveVcses = new AbstractVcs[0];
38 myEventDispatcher = eventDispatcher;
40 final ArrayList<VcsDirectoryMapping> listStr = new ArrayList<VcsDirectoryMapping>();
41 final VcsDirectoryMapping mapping = new VcsDirectoryMapping("", "");
42 listStr.add(mapping);
43 myVcsToPaths.put("", listStr);
44 mySortedMappings = new VcsDirectoryMapping[] {mapping};
47 public AbstractVcs[] getActiveVcses() {
48 synchronized (myLock) {
49 final AbstractVcs[] result = new AbstractVcs[myActiveVcses.length];
50 System.arraycopy(myActiveVcses, 0, result, 0, myActiveVcses.length);
51 return result;
55 @Modification
56 public void setMapping(final String path, final String activeVcsName) {
57 LOG.debug("setMapping path = '" + path + "' vcs = " + activeVcsName);
58 final VcsDirectoryMapping newMapping = new VcsDirectoryMapping(path, activeVcsName);
59 final LocalFileSystem.WatchRequest request = addWatchRequest(newMapping);
61 final Ref<Boolean> switched = new Ref<Boolean>(Boolean.FALSE);
62 keepActiveVcs(new Runnable() {
63 public void run() {
64 // sorted -> map. sorted mappings are NOT changed;
65 switched.set(trySwitchVcs(path, activeVcsName));
66 if (! switched.get().booleanValue()) {
67 final List<VcsDirectoryMapping> newList = listForVcsFromMap(newMapping.getVcs());
68 newList.add(newMapping);
69 sortedMappingsByMap();
71 if (request != null) {
72 myDirectoryMappingWatches.put(newMapping, request);
76 });
78 if (switched.get().booleanValue() && (request != null)) {
79 LocalFileSystem.getInstance().removeWatchedRoot(request);
82 mappingsChanged();
85 private void keepActiveVcs(final Runnable runnable) {
86 final MyVcsActivator activator;
87 synchronized (myLock) {
88 activator = new MyVcsActivator(new HashSet<String>(myVcsToPaths.keySet()));
89 runnable.run();
90 restoreActiveVcses();
92 activator.activate(myVcsToPaths.keySet(), AllVcses.getInstance(myProject));
95 private void restoreActiveVcses() {
96 synchronized (myLock) {
97 final Set<String> set = myVcsToPaths.keySet();
98 final List<AbstractVcs> list = new ArrayList<AbstractVcs>(set.size());
99 for (String s : set) {
100 if (s.trim().length() == 0) continue;
101 final AbstractVcs vcs = AllVcses.getInstance(myProject).getByName(s);
102 if (vcs != null) {
103 list.add(vcs);
106 myActiveVcses = list.toArray(new AbstractVcs[list.size()]);
110 public void mappingsChanged() {
111 myEventDispatcher.getMulticaster().directoryMappingChanged();
114 @Modification
115 public void setDirectoryMappings(final List<VcsDirectoryMapping> items) {
116 LOG.debug("setDirectoryMappings, size: " + items.size());
117 MySetMappingsPreProcessor setMappingsPreProcessor = new MySetMappingsPreProcessor(items);
118 setMappingsPreProcessor.invoke();
119 final List<VcsDirectoryMapping> itemsCopy = setMappingsPreProcessor.getItemsCopy();
120 final Map<VcsDirectoryMapping, LocalFileSystem.WatchRequest> requests = setMappingsPreProcessor.getRequests();
122 final Collection<LocalFileSystem.WatchRequest> toRemove = new ArrayList<LocalFileSystem.WatchRequest>();
124 keepActiveVcs(new Runnable() {
125 public void run() {
126 // a copy!
127 toRemove.addAll(myDirectoryMappingWatches.values());
128 myDirectoryMappingWatches.clear();
130 myVcsToPaths.clear();
131 for (VcsDirectoryMapping mapping : itemsCopy) {
132 listForVcsFromMap(mapping.getVcs()).add(mapping);
134 sortedMappingsByMap();
138 // do not remove what added and is active
139 toRemove.removeAll(requests.values());
140 // tracked by request object reference so OK to first add and then remove
141 LocalFileSystem.getInstance().removeWatchedRoots(toRemove);
143 mappingsChanged();
146 @Nullable
147 public VcsDirectoryMapping getMappingFor(VirtualFile file) {
148 if (file == null) return null;
149 if (! file.isInLocalFileSystem()) {
150 return null;
153 return getMappingFor(file, myDefaultVcsRootPolicy.getMatchContext(file));
156 @Nullable
157 public VcsDirectoryMapping getMappingFor(final VirtualFile file, final Object matchContext) {
158 // performance: calculate file path just once, rather than once per mapping
159 String path = file.getPath();
161 synchronized (myLock) {
162 for (int i = mySortedMappings.length - 1; i >= 0; -- i) {
163 final VcsDirectoryMapping mapping = mySortedMappings[i];
164 final String systemIndependPath = FileUtil.toSystemIndependentName((file.isDirectory() && (! path.endsWith("/"))) ? (path + "/") : path);
165 if (fileMatchesMapping(file, matchContext, systemIndependPath, mapping)) {
166 return mapping;
169 return null;
173 @Nullable
174 public String getVcsFor(@NotNull VirtualFile file) {
175 VcsDirectoryMapping mapping = getMappingFor(file);
176 if (mapping == null) {
177 return null;
179 return mapping.getVcs();
182 private boolean fileMatchesMapping(final VirtualFile file, final Object matchContext, final String systemIndependPath, final VcsDirectoryMapping mapping) {
183 if (mapping.getDirectory().length() == 0) {
184 return myDefaultVcsRootPolicy.matchesDefaultMapping(file, matchContext);
186 return FileUtil.startsWith(systemIndependPath, mapping.systemIndependentPath());
189 List<VirtualFile> getMappingsAsFilesUnderVcs(final AbstractVcs vcs) {
190 final List<VirtualFile> result = new ArrayList<VirtualFile>();
191 final String vcsName = vcs.getName();
193 final List<VcsDirectoryMapping> mappings;
194 synchronized (myLock) {
195 final List<VcsDirectoryMapping> vcsMappings = myVcsToPaths.get(vcsName);
196 if (vcsMappings == null) return result;
197 mappings = new ArrayList<VcsDirectoryMapping>(vcsMappings);
200 for (VcsDirectoryMapping mapping : mappings) {
201 if (mapping.isDefaultMapping()) {
202 // todo callback here; don't like it
203 myDefaultVcsRootPolicy.addDefaultVcsRoots(this, vcs, result);
204 } else {
205 final VirtualFile file = LocalFileSystem.getInstance().findFileByPath(mapping.getDirectory());
206 if (file != null) {
207 result.add(file);
211 return result;
214 @Modification
215 public void disposeMe() {
216 LOG.debug("dipose me");
217 clearImpl();
220 @Modification
221 public void clear() {
222 LOG.debug("clear");
223 clearImpl();
225 mappingsChanged();
228 private void clearImpl() {
229 // if vcses were not mapped, there's nothing to clear
230 if ((myActiveVcses == null) || (myActiveVcses.length == 0)) return;
232 final Collection<LocalFileSystem.WatchRequest> toRemove = new ArrayList<LocalFileSystem.WatchRequest>();
233 keepActiveVcs(new Runnable() {
234 public void run() {
235 // a copy!
236 toRemove.addAll(myDirectoryMappingWatches.values());
237 myDirectoryMappingWatches.clear();
239 myVcsToPaths.clear();
240 myActiveVcses = new AbstractVcs[0];
241 mySortedMappings = new VcsDirectoryMapping[0];
244 if (! toRemove.isEmpty()) {
245 LocalFileSystem.getInstance().removeWatchedRoots(toRemove);
249 public List<VcsDirectoryMapping> getDirectoryMappings() {
250 synchronized (myLock) {
251 return Arrays.asList(mySortedMappings);
255 public List<VcsDirectoryMapping> getDirectoryMappings(String vcsName) {
256 synchronized (myLock) {
257 final List<VcsDirectoryMapping> mappings = myVcsToPaths.get(vcsName);
258 return mappings == null ? new ArrayList<VcsDirectoryMapping>() : new ArrayList<VcsDirectoryMapping>(mappings);
262 public void cleanupMappings() {
263 final List<LocalFileSystem.WatchRequest> watchRequestList;
264 synchronized (myLock) {
265 watchRequestList = removeRedundantMappings();
267 LocalFileSystem.getInstance().removeWatchedRoots(watchRequestList);
270 @Nullable
271 public String haveDefaultMapping() {
272 synchronized (myLock) {
273 // empty mapping MUST be first
274 if (mySortedMappings.length == 0) return null;
275 return mySortedMappings[0].isDefaultMapping() ? mySortedMappings[0].getVcs() : null;
279 public boolean isEmpty() {
280 synchronized (myLock) {
281 return mySortedMappings.length == 0;
285 @Modification
286 public void removeDirectoryMapping(final VcsDirectoryMapping mapping) {
287 LOG.debug("remove mapping: " + mapping.getDirectory());
288 final Ref<LocalFileSystem.WatchRequest> request = new Ref<LocalFileSystem.WatchRequest>();
290 keepActiveVcs(new Runnable() {
291 public void run() {
292 if (removeVcsFromMap(mapping, mapping.getVcs())) {
293 sortedMappingsByMap();
294 request.set(myDirectoryMappingWatches.remove(mapping));
299 if (! request.isNull()) {
300 LocalFileSystem.getInstance().removeWatchedRoot(request.get());
303 mappingsChanged();
306 private class MyMappingsFilter extends AbstractFilterChildren<VcsDirectoryMapping> {
307 private final List<LocalFileSystem.WatchRequest> myRemovedRequests;
309 private MyMappingsFilter() {
310 myRemovedRequests = new ArrayList<LocalFileSystem.WatchRequest>();
313 protected void sortAscending(List<VcsDirectoryMapping> vcsDirectoryMappings) {
314 Collections.sort(vcsDirectoryMappings, MyMappingsComparator.getInstance());
317 @Override
318 protected void onRemove(final VcsDirectoryMapping vcsDirectoryMapping) {
319 final LocalFileSystem.WatchRequest request = myDirectoryMappingWatches.remove(vcsDirectoryMapping);
320 if (request != null) {
321 myRemovedRequests.add(request);
325 protected boolean isAncestor(VcsDirectoryMapping parent, VcsDirectoryMapping child) {
326 if (! parent.getVcs().equals(child.getVcs())) return false;
328 final String parentPath = parent.systemIndependentPath();
329 final String fixedParentPath = (parentPath.endsWith("/")) ? parentPath : (parentPath + "/");
331 if (child.systemIndependentPath().length() < fixedParentPath.length()) {
332 return child.systemIndependentPath().equals(parentPath);
334 return child.systemIndependentPath().startsWith(fixedParentPath);
337 public List<LocalFileSystem.WatchRequest> getRemovedRequests() {
338 return myRemovedRequests;
342 private List<LocalFileSystem.WatchRequest> removeRedundantMappings() {
343 final List<VcsDirectoryMapping> items = new ArrayList<VcsDirectoryMapping>(mySortedMappings.length);
344 Collections.addAll(items, mySortedMappings);
345 final MyMappingsFilter filter = new MyMappingsFilter();
346 filter.doFilter(items);
347 for (VcsDirectoryMapping mapping : items) {
348 listForVcsFromMap(mapping.getVcs()).add(mapping);
350 // already sorted
351 mySortedMappings = items.toArray(new VcsDirectoryMapping[items.size()]);
353 final List<LocalFileSystem.WatchRequest> watchRequestList = filter.getRemovedRequests();
354 return watchRequestList;
357 private boolean trySwitchVcs(final String path, final String activeVcsName) {
358 final String fixedPath = FileUtil.toSystemIndependentName(path);
359 for (VcsDirectoryMapping mapping : mySortedMappings) {
360 if (mapping.systemIndependentPath().equals(fixedPath)) {
361 final String oldVcs = mapping.getVcs();
362 if (! oldVcs.equals(activeVcsName)) {
363 migrateVcs(activeVcsName, mapping, oldVcs);
365 return true;
368 return false;
371 private void sortedMappingsByMap() {
372 final List<VcsDirectoryMapping> list = new ArrayList<VcsDirectoryMapping>();
373 for (List<VcsDirectoryMapping> mappingList : myVcsToPaths.values()) {
374 list.addAll(mappingList);
376 mySortedMappings = list.toArray(new VcsDirectoryMapping[list.size()]);
377 Arrays.sort(mySortedMappings, MyMappingsComparator.getInstance());
380 private void migrateVcs(String activeVcsName, VcsDirectoryMapping mapping, String oldVcs) {
381 mapping.setVcs(activeVcsName);
383 removeVcsFromMap(mapping, oldVcs);
385 final List<VcsDirectoryMapping> newList = listForVcsFromMap(activeVcsName);
386 newList.add(mapping);
389 private boolean removeVcsFromMap(VcsDirectoryMapping mapping, String oldVcs) {
390 final List<VcsDirectoryMapping> oldList = myVcsToPaths.get(oldVcs);
391 if (oldList == null) return false;
393 final boolean result = oldList.remove(mapping);
394 if (oldList.isEmpty()) {
395 myVcsToPaths.remove(oldVcs);
397 return result;
400 // todo don't like it
401 private List<VcsDirectoryMapping> listForVcsFromMap(String activeVcsName) {
402 List<VcsDirectoryMapping> newList = myVcsToPaths.get(activeVcsName);
403 if (newList == null) {
404 newList = new ArrayList<VcsDirectoryMapping>();
405 myVcsToPaths.put(activeVcsName, newList);
407 return newList;
410 @Nullable
411 private static LocalFileSystem.WatchRequest addWatchRequest(final VcsDirectoryMapping mapping) {
412 if (! mapping.isDefaultMapping()) {
413 return LocalFileSystem.getInstance().addRootToWatch(mapping.getDirectory(), true);
415 return null;
418 private static class MyMappingsComparator implements Comparator<VcsDirectoryMapping> {
419 private static final MyMappingsComparator ourInstance = new MyMappingsComparator();
421 public static MyMappingsComparator getInstance() {
422 return ourInstance;
425 public int compare(VcsDirectoryMapping m1, VcsDirectoryMapping m2) {
426 return m1.getDirectory().compareTo(m2.getDirectory());
430 private static class MyVcsActivator {
431 private final Set<String> myOld;
433 public MyVcsActivator(final Set<String> old) {
434 myOld = old;
437 public void activate(final Set<String> newOne, final AllVcsesI vcsesI) {
438 final Set<String> toAdd = notInBottom(newOne, myOld);
439 final Set<String> toRemove = notInBottom(myOld, newOne);
440 if (toAdd != null) {
441 for (String s : toAdd) {
442 final AbstractVcs vcs = vcsesI.getByName(s);
443 if (vcs != null) {
444 try {
445 vcs.doActivate();
447 catch (VcsException e) {
448 // actually is not thrown (AbstractVcs#actualActivate())
450 } else {
451 LOG.info("Error: activating non existing vcs");
455 if (toRemove != null) {
456 for (String s : toRemove) {
457 final AbstractVcs vcs = vcsesI.getByName(s);
458 if (vcs != null) {
459 try {
460 vcs.doDeactivate();
462 catch (VcsException e) {
463 // actually is not thrown (AbstractVcs#actualDeactivate())
465 } else {
466 LOG.info("Error: removing non existing vcs");
472 @Nullable
473 private Set<String> notInBottom(final Set<String> top, final Set<String> bottom) {
474 Set<String> notInBottom = null;
475 for (String topItem : top) {
476 // omit empty vcs: not a vcs
477 if (topItem.trim().length() == 0) continue;
479 if (! bottom.contains(topItem)) {
480 if (notInBottom == null) {
481 notInBottom = new HashSet<String>();
483 notInBottom.add(topItem);
486 return notInBottom;
490 public boolean haveActiveVcs(final String name) {
491 synchronized (myLock) {
492 return myVcsToPaths.containsKey(name);
496 @Modification
497 public void beingUnregistered(final String name) {
498 synchronized (myLock) {
499 keepActiveVcs(new Runnable() {
500 public void run() {
501 final List<VcsDirectoryMapping> removed = myVcsToPaths.remove(name);
502 sortedMappingsByMap();
507 mappingsChanged();
510 private static class MySetMappingsPreProcessor {
511 private List<VcsDirectoryMapping> myItems;
512 private List<VcsDirectoryMapping> myItemsCopy;
513 private Map<VcsDirectoryMapping, LocalFileSystem.WatchRequest> myRequests;
515 public MySetMappingsPreProcessor(final List<VcsDirectoryMapping> items) {
516 myItems = items;
519 public List<VcsDirectoryMapping> getItemsCopy() {
520 return myItemsCopy;
523 public Map<VcsDirectoryMapping, LocalFileSystem.WatchRequest> getRequests() {
524 return myRequests;
527 public void invoke() {
528 if (myItems.isEmpty()) {
529 myItemsCopy = Collections.singletonList(new VcsDirectoryMapping("", ""));
530 myRequests = Collections.emptyMap();
531 } else {
532 myRequests = new HashMap<VcsDirectoryMapping, LocalFileSystem.WatchRequest>();
534 for (VcsDirectoryMapping item : myItems) {
535 final LocalFileSystem.WatchRequest request = addWatchRequest(item);
536 if (request != null) {
537 myRequests.put(item, request);
540 myItemsCopy = myItems;
545 private @interface Modification {