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
.DumbAwareRunnable
;
20 import com
.intellij
.openapi
.project
.Project
;
21 import com
.intellij
.openapi
.startup
.StartupManager
;
22 import com
.intellij
.openapi
.ui
.MessageType
;
23 import com
.intellij
.openapi
.util
.EmptyRunnable
;
24 import com
.intellij
.openapi
.util
.Pair
;
25 import com
.intellij
.openapi
.util
.Ref
;
26 import com
.intellij
.openapi
.util
.io
.FileUtil
;
27 import com
.intellij
.openapi
.util
.text
.StringUtil
;
28 import com
.intellij
.openapi
.vcs
.*;
29 import com
.intellij
.openapi
.vcs
.changes
.ui
.ChangesViewBalloonProblemNotifier
;
30 import com
.intellij
.openapi
.vcs
.impl
.DefaultVcsRootPolicy
;
31 import com
.intellij
.openapi
.vcs
.impl
.ProjectLevelVcsManagerImpl
;
32 import com
.intellij
.openapi
.vcs
.impl
.VcsInitObject
;
33 import com
.intellij
.openapi
.vfs
.LocalFileSystem
;
34 import com
.intellij
.openapi
.vfs
.VirtualFile
;
35 import com
.intellij
.util
.EventDispatcher
;
36 import com
.intellij
.util
.containers
.Convertor
;
37 import org
.jetbrains
.annotations
.NotNull
;
38 import org
.jetbrains
.annotations
.Nullable
;
42 public class NewMappings
{
43 private final static Logger LOG
= Logger
.getInstance("#com.intellij.openapi.vcs.impl.projectlevelman.NewMappings");
44 private final Object myLock
;
47 private final Map
<String
, List
<VcsDirectoryMapping
>> myVcsToPaths
;
48 private AbstractVcs
[] myActiveVcses
;
49 private VcsDirectoryMapping
[] mySortedMappings
;
50 private final Map
<VcsDirectoryMapping
, LocalFileSystem
.WatchRequest
> myDirectoryMappingWatches
;
52 private final DefaultVcsRootPolicy myDefaultVcsRootPolicy
;
53 private final EventDispatcher
<VcsListener
> myEventDispatcher
;
54 private final Project myProject
;
56 private boolean myActivated
;
58 public NewMappings(final Project project
, final EventDispatcher
<VcsListener
> eventDispatcher
,
59 final ProjectLevelVcsManagerImpl vcsManager
) {
61 myLock
= new Object();
62 myVcsToPaths
= new HashMap
<String
, List
<VcsDirectoryMapping
>>();
63 myDirectoryMappingWatches
= new HashMap
<VcsDirectoryMapping
, LocalFileSystem
.WatchRequest
>();
64 myDefaultVcsRootPolicy
= DefaultVcsRootPolicy
.getInstance(project
);
65 myActiveVcses
= new AbstractVcs
[0];
66 myEventDispatcher
= eventDispatcher
;
68 final ArrayList
<VcsDirectoryMapping
> listStr
= new ArrayList
<VcsDirectoryMapping
>();
69 final VcsDirectoryMapping mapping
= new VcsDirectoryMapping("", "");
71 myVcsToPaths
.put("", listStr
);
72 mySortedMappings
= new VcsDirectoryMapping
[] {mapping
};
75 vcsManager
.addInitializationRequest(VcsInitObject
.MAPPINGS
, new DumbAwareRunnable() {
77 activateActiveVcses();
82 public AbstractVcs
[] getActiveVcses() {
83 synchronized (myLock
) {
84 final AbstractVcs
[] result
= new AbstractVcs
[myActiveVcses
.length
];
85 System
.arraycopy(myActiveVcses
, 0, result
, 0, myActiveVcses
.length
);
90 public void activateActiveVcses() {
91 synchronized (myLock
) {
92 if (myActivated
) return;
95 keepActiveVcs(EmptyRunnable
.getInstance());
100 public void setMapping(final String path
, final String activeVcsName
) {
101 LOG
.debug("setMapping path = '" + path
+ "' vcs = " + activeVcsName
);
102 final VcsDirectoryMapping newMapping
= new VcsDirectoryMapping(path
, activeVcsName
);
103 // do not add duplicates
104 synchronized (myLock
) {
105 if (myVcsToPaths
.containsKey(activeVcsName
)) {
106 final List
<VcsDirectoryMapping
> vcsDirectoryMappings
= myVcsToPaths
.get(activeVcsName
);
107 if ((vcsDirectoryMappings
!= null) && (vcsDirectoryMappings
.contains(newMapping
))) {
113 final LocalFileSystem
.WatchRequest request
= addWatchRequest(newMapping
);
115 final Ref
<Boolean
> switched
= new Ref
<Boolean
>(Boolean
.FALSE
);
116 keepActiveVcs(new Runnable() {
118 // sorted -> map. sorted mappings are NOT changed;
119 switched
.set(trySwitchVcs(path
, activeVcsName
));
120 if (! switched
.get().booleanValue()) {
121 final List
<VcsDirectoryMapping
> newList
= listForVcsFromMap(newMapping
.getVcs());
122 newList
.add(newMapping
);
123 sortedMappingsByMap();
125 if (request
!= null) {
126 myDirectoryMappingWatches
.put(newMapping
, request
);
132 if (switched
.get().booleanValue() && (request
!= null)) {
133 LocalFileSystem
.getInstance().removeWatchedRoot(request
);
139 private void keepActiveVcs(final Runnable runnable
) {
140 final MyVcsActivator activator
;
141 synchronized (myLock
) {
146 final HashSet
<String
> old
= new HashSet
<String
>();
147 for (AbstractVcs activeVcs
: myActiveVcses
) {
148 old
.add(activeVcs
.getName());
150 activator
= new MyVcsActivator(old
);
152 restoreActiveVcses();
154 activator
.activate(myVcsToPaths
.keySet(), AllVcses
.getInstance(myProject
));
157 private void restoreActiveVcses() {
158 synchronized (myLock
) {
159 final Set
<String
> set
= myVcsToPaths
.keySet();
160 final List
<AbstractVcs
> list
= new ArrayList
<AbstractVcs
>(set
.size());
161 for (String s
: set
) {
162 if (s
.trim().length() == 0) continue;
163 final AbstractVcs vcs
= AllVcses
.getInstance(myProject
).getByName(s
);
168 myActiveVcses
= list
.toArray(new AbstractVcs
[list
.size()]);
172 public void mappingsChanged() {
173 myEventDispatcher
.getMulticaster().directoryMappingChanged();
177 public void setDirectoryMappings(final List
<VcsDirectoryMapping
> items
) {
178 LOG
.debug("setDirectoryMappings, size: " + items
.size());
179 MySetMappingsPreProcessor setMappingsPreProcessor
= new MySetMappingsPreProcessor(items
);
180 setMappingsPreProcessor
.invoke();
181 final List
<VcsDirectoryMapping
> itemsCopy
= setMappingsPreProcessor
.getItemsCopy();
182 final Map
<VcsDirectoryMapping
, LocalFileSystem
.WatchRequest
> requests
= setMappingsPreProcessor
.getRequests();
184 final Collection
<LocalFileSystem
.WatchRequest
> toRemove
= new ArrayList
<LocalFileSystem
.WatchRequest
>();
186 keepActiveVcs(new Runnable() {
189 toRemove
.addAll(myDirectoryMappingWatches
.values());
190 myDirectoryMappingWatches
.clear();
192 myVcsToPaths
.clear();
193 for (VcsDirectoryMapping mapping
: itemsCopy
) {
194 listForVcsFromMap(mapping
.getVcs()).add(mapping
);
196 sortedMappingsByMap();
200 // do not remove what added and is active
201 toRemove
.removeAll(requests
.values());
202 // tracked by request object reference so OK to first add and then remove
203 LocalFileSystem
.getInstance().removeWatchedRoots(toRemove
);
209 public VcsDirectoryMapping
getMappingFor(VirtualFile file
) {
210 if (file
== null) return null;
211 if (! file
.isInLocalFileSystem()) {
215 return getMappingFor(file
, myDefaultVcsRootPolicy
.getMatchContext(file
));
219 public VcsDirectoryMapping
getMappingFor(final VirtualFile file
, final Object matchContext
) {
220 // performance: calculate file path just once, rather than once per mapping
221 String path
= file
.getPath();
222 final String systemIndependPath
= FileUtil
.toSystemIndependentName((file
.isDirectory() && (! path
.endsWith("/"))) ?
(path
+ "/") : path
);
224 synchronized (myLock
) {
225 for (int i
= mySortedMappings
.length
- 1; i
>= 0; -- i
) {
226 final VcsDirectoryMapping mapping
= mySortedMappings
[i
];
227 if (fileMatchesMapping(file
, matchContext
, systemIndependPath
, mapping
)) {
236 public String
getVcsFor(@NotNull VirtualFile file
) {
237 VcsDirectoryMapping mapping
= getMappingFor(file
);
238 if (mapping
== null) {
241 return mapping
.getVcs();
244 private boolean fileMatchesMapping(final VirtualFile file
, final Object matchContext
, final String systemIndependPath
, final VcsDirectoryMapping mapping
) {
245 if (mapping
.getDirectory().length() == 0) {
246 return myDefaultVcsRootPolicy
.matchesDefaultMapping(file
, matchContext
);
248 return FileUtil
.startsWith(systemIndependPath
, mapping
.systemIndependentPath());
251 List
<VirtualFile
> getMappingsAsFilesUnderVcs(final AbstractVcs vcs
) {
252 final List
<VirtualFile
> result
= new ArrayList
<VirtualFile
>();
253 final String vcsName
= vcs
.getName();
255 final List
<VcsDirectoryMapping
> mappings
;
256 synchronized (myLock
) {
257 final List
<VcsDirectoryMapping
> vcsMappings
= myVcsToPaths
.get(vcsName
);
258 if (vcsMappings
== null) return result
;
259 mappings
= new ArrayList
<VcsDirectoryMapping
>(vcsMappings
);
262 for (VcsDirectoryMapping mapping
: mappings
) {
263 if (mapping
.isDefaultMapping()) {
264 // todo callback here; don't like it
265 myDefaultVcsRootPolicy
.addDefaultVcsRoots(this, vcs
, result
);
267 final VirtualFile file
= LocalFileSystem
.getInstance().findFileByPath(mapping
.getDirectory());
277 public void disposeMe() {
278 LOG
.debug("dipose me");
283 public void clear() {
290 private void clearImpl() {
291 // if vcses were not mapped, there's nothing to clear
292 if ((myActiveVcses
== null) || (myActiveVcses
.length
== 0)) return;
294 final Collection
<LocalFileSystem
.WatchRequest
> toRemove
= new ArrayList
<LocalFileSystem
.WatchRequest
>();
295 keepActiveVcs(new Runnable() {
298 toRemove
.addAll(myDirectoryMappingWatches
.values());
299 myDirectoryMappingWatches
.clear();
301 myVcsToPaths
.clear();
302 myActiveVcses
= new AbstractVcs
[0];
303 mySortedMappings
= new VcsDirectoryMapping
[0];
306 if (! toRemove
.isEmpty()) {
307 LocalFileSystem
.getInstance().removeWatchedRoots(toRemove
);
311 public List
<VcsDirectoryMapping
> getDirectoryMappings() {
312 synchronized (myLock
) {
313 return Arrays
.asList(mySortedMappings
);
317 public List
<VcsDirectoryMapping
> getDirectoryMappings(String vcsName
) {
318 synchronized (myLock
) {
319 final List
<VcsDirectoryMapping
> mappings
= myVcsToPaths
.get(vcsName
);
320 return mappings
== null ?
new ArrayList
<VcsDirectoryMapping
>() : new ArrayList
<VcsDirectoryMapping
>(mappings
);
324 public void cleanupMappings() {
325 final List
<LocalFileSystem
.WatchRequest
> watchRequestList
;
326 synchronized (myLock
) {
327 watchRequestList
= removeRedundantMappings();
329 LocalFileSystem
.getInstance().removeWatchedRoots(watchRequestList
);
333 public String
haveDefaultMapping() {
334 synchronized (myLock
) {
335 // empty mapping MUST be first
336 if (mySortedMappings
.length
== 0) return null;
337 return mySortedMappings
[0].isDefaultMapping() ? mySortedMappings
[0].getVcs() : null;
341 public boolean isEmpty() {
342 synchronized (myLock
) {
343 return mySortedMappings
.length
== 0;
348 public void removeDirectoryMapping(final VcsDirectoryMapping mapping
) {
349 LOG
.debug("remove mapping: " + mapping
.getDirectory());
350 final Ref
<LocalFileSystem
.WatchRequest
> request
= new Ref
<LocalFileSystem
.WatchRequest
>();
352 keepActiveVcs(new Runnable() {
354 if (removeVcsFromMap(mapping
, mapping
.getVcs())) {
355 sortedMappingsByMap();
356 request
.set(myDirectoryMappingWatches
.remove(mapping
));
361 if (! request
.isNull()) {
362 LocalFileSystem
.getInstance().removeWatchedRoot(request
.get());
368 private class MyMappingsFilter
extends AbstractFilterChildren
<VcsDirectoryMapping
> {
369 private final List
<LocalFileSystem
.WatchRequest
> myRemovedRequests
;
371 private MyMappingsFilter() {
372 myRemovedRequests
= new ArrayList
<LocalFileSystem
.WatchRequest
>();
375 protected void sortAscending(List
<VcsDirectoryMapping
> vcsDirectoryMappings
) {
376 // todo ordering is actually here
377 Collections
.sort(vcsDirectoryMappings
, MyMappingsComparator
.getInstance());
381 protected void onRemove(final VcsDirectoryMapping vcsDirectoryMapping
) {
382 final LocalFileSystem
.WatchRequest request
= myDirectoryMappingWatches
.remove(vcsDirectoryMapping
);
383 if (request
!= null) {
384 myRemovedRequests
.add(request
);
388 protected boolean isAncestor(VcsDirectoryMapping parent
, VcsDirectoryMapping child
) {
389 if (! parent
.getVcs().equals(child
.getVcs())) return false;
391 final String parentPath
= parent
.systemIndependentPath();
392 final String fixedParentPath
= (parentPath
.endsWith("/")) ? parentPath
: (parentPath
+ "/");
394 if (child
.systemIndependentPath().length() < fixedParentPath
.length()) {
395 return child
.systemIndependentPath().equals(parentPath
);
397 return child
.systemIndependentPath().startsWith(fixedParentPath
);
400 public List
<LocalFileSystem
.WatchRequest
> getRemovedRequests() {
401 return myRemovedRequests
;
405 // todo area for optimization
406 private List
<LocalFileSystem
.WatchRequest
> removeRedundantMappings() {
407 final Set
<Map
.Entry
<String
, List
<VcsDirectoryMapping
>>> entries
= myVcsToPaths
.entrySet();
409 final LocalFileSystem lfs
= LocalFileSystem
.getInstance();
410 final AllVcsesI allVcses
= AllVcses
.getInstance(myProject
);
412 final List
<LocalFileSystem
.WatchRequest
> removedRequests
= new LinkedList
<LocalFileSystem
.WatchRequest
>();
414 for (Iterator
<String
> iterator
= myVcsToPaths
.keySet().iterator(); iterator
.hasNext();) {
415 final String vcsName
= iterator
.next();
416 final List
<VcsDirectoryMapping
> mappings
= myVcsToPaths
.get(vcsName
);
418 final List
<Pair
<VirtualFile
, VcsDirectoryMapping
>> objects
= ObjectsConvertor
.convert(mappings
,
419 new Convertor
<VcsDirectoryMapping
, Pair
<VirtualFile
, VcsDirectoryMapping
>>() {
420 public Pair
<VirtualFile
, VcsDirectoryMapping
> convert(final VcsDirectoryMapping dm
) {
421 VirtualFile vf
= lfs
.findFileByPath(dm
.getDirectory());
423 vf
= lfs
.refreshAndFindFileByPath(dm
.getDirectory());
425 return vf
== null ?
null : new Pair
<VirtualFile
, VcsDirectoryMapping
>(vf
, dm
);
427 }, ObjectsConvertor
.NOT_NULL
);
429 final List
<Pair
<VirtualFile
, VcsDirectoryMapping
>> filteredFiles
;
431 final Convertor
<Pair
<VirtualFile
, VcsDirectoryMapping
>, VirtualFile
> fileConvertor
=
432 new Convertor
<Pair
<VirtualFile
, VcsDirectoryMapping
>, VirtualFile
>() {
433 public VirtualFile
convert(Pair
<VirtualFile
, VcsDirectoryMapping
> o
) {
437 if (StringUtil
.isEmptyOrSpaces(vcsName
)) {
438 filteredFiles
= AbstractVcs
.filterUniqueRootsDefault(objects
, fileConvertor
);
440 final AbstractVcs vcs
= allVcses
.getByName(vcsName
);
442 ChangesViewBalloonProblemNotifier
.showMe(myProject
, "VCS plugin not found for mapping to : '" + vcsName
+ "'", MessageType
.ERROR
);
445 filteredFiles
= vcs
.filterUniqueRoots(objects
, fileConvertor
);
448 final List
<VcsDirectoryMapping
> filteredMappings
=
449 ObjectsConvertor
.convert(filteredFiles
, new Convertor
<Pair
<VirtualFile
, VcsDirectoryMapping
>, VcsDirectoryMapping
>() {
450 public VcsDirectoryMapping
convert(final Pair
<VirtualFile
, VcsDirectoryMapping
> o
) {
451 return o
.getSecond();
455 // to calculate what had been removed
456 mappings
.removeAll(filteredMappings
);
457 for (VcsDirectoryMapping mapping
: mappings
) {
458 removedRequests
.add(myDirectoryMappingWatches
.remove(mapping
));
461 if (filteredMappings
.isEmpty()) {
465 mappings
.addAll(filteredMappings
);
469 sortedMappingsByMap();
470 return removedRequests
;
473 private boolean trySwitchVcs(final String path
, final String activeVcsName
) {
474 final String fixedPath
= FileUtil
.toSystemIndependentName(path
);
475 for (VcsDirectoryMapping mapping
: mySortedMappings
) {
476 if (mapping
.systemIndependentPath().equals(fixedPath
)) {
477 final String oldVcs
= mapping
.getVcs();
478 if (! oldVcs
.equals(activeVcsName
)) {
479 migrateVcs(activeVcsName
, mapping
, oldVcs
);
487 private void sortedMappingsByMap() {
488 final List
<VcsDirectoryMapping
> list
= new ArrayList
<VcsDirectoryMapping
>();
489 for (List
<VcsDirectoryMapping
> mappingList
: myVcsToPaths
.values()) {
490 list
.addAll(mappingList
);
492 mySortedMappings
= list
.toArray(new VcsDirectoryMapping
[list
.size()]);
493 Arrays
.sort(mySortedMappings
, MyMappingsComparator
.getInstance());
496 private void migrateVcs(String activeVcsName
, VcsDirectoryMapping mapping
, String oldVcs
) {
497 mapping
.setVcs(activeVcsName
);
499 removeVcsFromMap(mapping
, oldVcs
);
501 final List
<VcsDirectoryMapping
> newList
= listForVcsFromMap(activeVcsName
);
502 newList
.add(mapping
);
505 private boolean removeVcsFromMap(VcsDirectoryMapping mapping
, String oldVcs
) {
506 final List
<VcsDirectoryMapping
> oldList
= myVcsToPaths
.get(oldVcs
);
507 if (oldList
== null) return false;
509 final boolean result
= oldList
.remove(mapping
);
510 if (oldList
.isEmpty()) {
511 myVcsToPaths
.remove(oldVcs
);
516 // todo don't like it
517 private List
<VcsDirectoryMapping
> listForVcsFromMap(String activeVcsName
) {
518 List
<VcsDirectoryMapping
> newList
= myVcsToPaths
.get(activeVcsName
);
519 if (newList
== null) {
520 newList
= new ArrayList
<VcsDirectoryMapping
>();
521 myVcsToPaths
.put(activeVcsName
, newList
);
527 private static LocalFileSystem
.WatchRequest
addWatchRequest(final VcsDirectoryMapping mapping
) {
528 if (! mapping
.isDefaultMapping()) {
529 return LocalFileSystem
.getInstance().addRootToWatch(mapping
.getDirectory(), true);
534 private static class MyMappingsComparator
implements Comparator
<VcsDirectoryMapping
> {
535 private static final MyMappingsComparator ourInstance
= new MyMappingsComparator();
537 public static MyMappingsComparator
getInstance() {
541 public int compare(VcsDirectoryMapping m1
, VcsDirectoryMapping m2
) {
542 return m1
.getDirectory().compareTo(m2
.getDirectory());
546 private static class MyVcsActivator
{
547 private final Set
<String
> myOld
;
549 public MyVcsActivator(final Set
<String
> old
) {
553 public void activate(final Set
<String
> newOne
, final AllVcsesI vcsesI
) {
554 final Set
<String
> toAdd
= notInBottom(newOne
, myOld
);
555 final Set
<String
> toRemove
= notInBottom(myOld
, newOne
);
557 for (String s
: toAdd
) {
558 final AbstractVcs vcs
= vcsesI
.getByName(s
);
563 catch (VcsException e
) {
564 // actually is not thrown (AbstractVcs#actualActivate())
567 LOG
.info("Error: activating non existing vcs: " + s
);
571 if (toRemove
!= null) {
572 for (String s
: toRemove
) {
573 final AbstractVcs vcs
= vcsesI
.getByName(s
);
578 catch (VcsException e
) {
579 // actually is not thrown (AbstractVcs#actualDeactivate())
582 LOG
.info("Error: removing non existing vcs: " + s
);
589 private Set
<String
> notInBottom(final Set
<String
> top
, final Set
<String
> bottom
) {
590 Set
<String
> notInBottom
= null;
591 for (String topItem
: top
) {
592 // omit empty vcs: not a vcs
593 if (topItem
.trim().length() == 0) continue;
595 if (! bottom
.contains(topItem
)) {
596 if (notInBottom
== null) {
597 notInBottom
= new HashSet
<String
>();
599 notInBottom
.add(topItem
);
606 public boolean haveActiveVcs(final String name
) {
607 synchronized (myLock
) {
608 return myVcsToPaths
.containsKey(name
);
613 public void beingUnregistered(final String name
) {
614 synchronized (myLock
) {
615 keepActiveVcs(new Runnable() {
617 final List
<VcsDirectoryMapping
> removed
= myVcsToPaths
.remove(name
);
618 sortedMappingsByMap();
626 private static class MySetMappingsPreProcessor
{
627 private List
<VcsDirectoryMapping
> myItems
;
628 private List
<VcsDirectoryMapping
> myItemsCopy
;
629 private Map
<VcsDirectoryMapping
, LocalFileSystem
.WatchRequest
> myRequests
;
631 public MySetMappingsPreProcessor(final List
<VcsDirectoryMapping
> items
) {
635 public List
<VcsDirectoryMapping
> getItemsCopy() {
639 public Map
<VcsDirectoryMapping
, LocalFileSystem
.WatchRequest
> getRequests() {
643 public void invoke() {
644 if (myItems
.isEmpty()) {
645 myItemsCopy
= Collections
.singletonList(new VcsDirectoryMapping("", ""));
646 myRequests
= Collections
.emptyMap();
648 myRequests
= new HashMap
<VcsDirectoryMapping
, LocalFileSystem
.WatchRequest
>();
650 for (VcsDirectoryMapping item
: myItems
) {
651 final LocalFileSystem
.WatchRequest request
= addWatchRequest(item
);
652 if (request
!= null) {
653 myRequests
.put(item
, request
);
656 myItemsCopy
= myItems
;
661 private @interface Modification
{