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
;
17 public class NewMappings
{
18 private final static Logger LOG
= Logger
.getInstance("#com.intellij.openapi.vcs.impl.projectlevelman.NewMappings");
19 private final Object myLock
;
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
) {
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("", "");
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
);
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() {
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
);
78 if (switched
.get().booleanValue() && (request
!= null)) {
79 LocalFileSystem
.getInstance().removeWatchedRoot(request
);
85 private void keepActiveVcs(final Runnable runnable
) {
86 final MyVcsActivator activator
;
87 synchronized (myLock
) {
88 activator
= new MyVcsActivator(new HashSet
<String
>(myVcsToPaths
.keySet()));
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
);
106 myActiveVcses
= list
.toArray(new AbstractVcs
[list
.size()]);
110 public void mappingsChanged() {
111 myEventDispatcher
.getMulticaster().directoryMappingChanged();
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() {
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
);
147 public VcsDirectoryMapping
getMappingFor(VirtualFile file
) {
148 if (file
== null) return null;
149 if (! file
.isInLocalFileSystem()) {
153 return getMappingFor(file
, myDefaultVcsRootPolicy
.getMatchContext(file
));
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
)) {
174 public String
getVcsFor(@NotNull VirtualFile file
) {
175 VcsDirectoryMapping mapping
= getMappingFor(file
);
176 if (mapping
== 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
);
205 final VirtualFile file
= LocalFileSystem
.getInstance().findFileByPath(mapping
.getDirectory());
215 public void disposeMe() {
216 LOG
.debug("dipose me");
221 public void clear() {
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() {
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
);
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;
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() {
292 if (removeVcsFromMap(mapping
, mapping
.getVcs())) {
293 sortedMappingsByMap();
294 request
.set(myDirectoryMappingWatches
.remove(mapping
));
299 if (! request
.isNull()) {
300 LocalFileSystem
.getInstance().removeWatchedRoot(request
.get());
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());
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
);
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
);
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
);
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
);
411 private static LocalFileSystem
.WatchRequest
addWatchRequest(final VcsDirectoryMapping mapping
) {
412 if (! mapping
.isDefaultMapping()) {
413 return LocalFileSystem
.getInstance().addRootToWatch(mapping
.getDirectory(), true);
418 private static class MyMappingsComparator
implements Comparator
<VcsDirectoryMapping
> {
419 private static final MyMappingsComparator ourInstance
= new MyMappingsComparator();
421 public static MyMappingsComparator
getInstance() {
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
) {
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
);
441 for (String s
: toAdd
) {
442 final AbstractVcs vcs
= vcsesI
.getByName(s
);
447 catch (VcsException e
) {
448 // actually is not thrown (AbstractVcs#actualActivate())
451 LOG
.info("Error: activating non existing vcs");
455 if (toRemove
!= null) {
456 for (String s
: toRemove
) {
457 final AbstractVcs vcs
= vcsesI
.getByName(s
);
462 catch (VcsException e
) {
463 // actually is not thrown (AbstractVcs#actualDeactivate())
466 LOG
.info("Error: removing non existing vcs");
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
);
490 public boolean haveActiveVcs(final String name
) {
491 synchronized (myLock
) {
492 return myVcsToPaths
.containsKey(name
);
497 public void beingUnregistered(final String name
) {
498 synchronized (myLock
) {
499 keepActiveVcs(new Runnable() {
501 final List
<VcsDirectoryMapping
> removed
= myVcsToPaths
.remove(name
);
502 sortedMappingsByMap();
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
) {
519 public List
<VcsDirectoryMapping
> getItemsCopy() {
523 public Map
<VcsDirectoryMapping
, LocalFileSystem
.WatchRequest
> getRequests() {
527 public void invoke() {
528 if (myItems
.isEmpty()) {
529 myItemsCopy
= Collections
.singletonList(new VcsDirectoryMapping("", ""));
530 myRequests
= Collections
.emptyMap();
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
{