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
;
35 public class NewMappings
{
36 private final static Logger LOG
= Logger
.getInstance("#com.intellij.openapi.vcs.impl.projectlevelman.NewMappings");
37 private final Object myLock
;
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
) {
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("", "");
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
);
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() {
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
);
96 if (switched
.get().booleanValue() && (request
!= null)) {
97 LocalFileSystem
.getInstance().removeWatchedRoot(request
);
103 private void keepActiveVcs(final Runnable runnable
) {
104 final MyVcsActivator activator
;
105 synchronized (myLock
) {
106 activator
= new MyVcsActivator(new HashSet
<String
>(myVcsToPaths
.keySet()));
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
);
124 myActiveVcses
= list
.toArray(new AbstractVcs
[list
.size()]);
128 public void mappingsChanged() {
129 myEventDispatcher
.getMulticaster().directoryMappingChanged();
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() {
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
);
165 public VcsDirectoryMapping
getMappingFor(VirtualFile file
) {
166 if (file
== null) return null;
167 if (! file
.isInLocalFileSystem()) {
171 return getMappingFor(file
, myDefaultVcsRootPolicy
.getMatchContext(file
));
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
)) {
192 public String
getVcsFor(@NotNull VirtualFile file
) {
193 VcsDirectoryMapping mapping
= getMappingFor(file
);
194 if (mapping
== 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
);
223 final VirtualFile file
= LocalFileSystem
.getInstance().findFileByPath(mapping
.getDirectory());
233 public void disposeMe() {
234 LOG
.debug("dipose me");
239 public void clear() {
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() {
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
);
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;
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() {
310 if (removeVcsFromMap(mapping
, mapping
.getVcs())) {
311 sortedMappingsByMap();
312 request
.set(myDirectoryMappingWatches
.remove(mapping
));
317 if (! request
.isNull()) {
318 LocalFileSystem
.getInstance().removeWatchedRoot(request
.get());
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());
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());
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
;
387 final Convertor
<Pair
<VirtualFile
, VcsDirectoryMapping
>, VirtualFile
> fileConvertor
=
388 new Convertor
<Pair
<VirtualFile
, VcsDirectoryMapping
>, VirtualFile
>() {
389 public VirtualFile
convert(Pair
<VirtualFile
, VcsDirectoryMapping
> o
) {
393 if (StringUtil
.isEmptyOrSpaces(vcsName
)) {
394 filteredFiles
= AbstractVcs
.filterUniqueRootsDefault(objects
, fileConvertor
);
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()) {
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
);
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
);
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
);
479 private static LocalFileSystem
.WatchRequest
addWatchRequest(final VcsDirectoryMapping mapping
) {
480 if (! mapping
.isDefaultMapping()) {
481 return LocalFileSystem
.getInstance().addRootToWatch(mapping
.getDirectory(), true);
486 private static class MyMappingsComparator
implements Comparator
<VcsDirectoryMapping
> {
487 private static final MyMappingsComparator ourInstance
= new MyMappingsComparator();
489 public static MyMappingsComparator
getInstance() {
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
) {
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
);
509 for (String s
: toAdd
) {
510 final AbstractVcs vcs
= vcsesI
.getByName(s
);
515 catch (VcsException e
) {
516 // actually is not thrown (AbstractVcs#actualActivate())
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
);
530 catch (VcsException e
) {
531 // actually is not thrown (AbstractVcs#actualDeactivate())
534 LOG
.info("Error: removing non existing vcs: " + s
);
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
);
558 public boolean haveActiveVcs(final String name
) {
559 synchronized (myLock
) {
560 return myVcsToPaths
.containsKey(name
);
565 public void beingUnregistered(final String name
) {
566 synchronized (myLock
) {
567 keepActiveVcs(new Runnable() {
569 final List
<VcsDirectoryMapping
> removed
= myVcsToPaths
.remove(name
);
570 sortedMappingsByMap();
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
) {
587 public List
<VcsDirectoryMapping
> getItemsCopy() {
591 public Map
<VcsDirectoryMapping
, LocalFileSystem
.WatchRequest
> getRequests() {
595 public void invoke() {
596 if (myItems
.isEmpty()) {
597 myItemsCopy
= Collections
.singletonList(new VcsDirectoryMapping("", ""));
598 myRequests
= Collections
.emptyMap();
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
{