1 package com
.intellij
.openapi
.vcs
.changes
.ui
;
3 import com
.intellij
.openapi
.project
.Project
;
4 import com
.intellij
.openapi
.util
.Computable
;
5 import com
.intellij
.openapi
.vcs
.FilePath
;
6 import com
.intellij
.openapi
.vcs
.FilePathImpl
;
7 import com
.intellij
.openapi
.vcs
.VcsBundle
;
8 import com
.intellij
.openapi
.vcs
.changes
.*;
9 import com
.intellij
.openapi
.vfs
.VirtualFile
;
10 import com
.intellij
.util
.containers
.MultiMap
;
11 import com
.intellij
.util
.ui
.tree
.TreeUtil
;
12 import org
.jetbrains
.annotations
.NonNls
;
13 import org
.jetbrains
.annotations
.Nullable
;
15 import javax
.swing
.tree
.DefaultTreeModel
;
16 import javax
.swing
.tree
.MutableTreeNode
;
17 import javax
.swing
.tree
.TreeNode
;
23 public class TreeModelBuilder
{
24 @NonNls public static final String ROOT_NODE_VALUE
= "root";
26 private final Project myProject
;
27 private final boolean showFlatten
;
28 private final DefaultTreeModel model
;
29 private final ChangesBrowserNode root
;
31 public TreeModelBuilder(final Project project
, final boolean showFlatten
) {
33 this.showFlatten
= showFlatten
;
34 root
= ChangesBrowserNode
.create(myProject
, ROOT_NODE_VALUE
);
35 model
= new DefaultTreeModel(root
);
38 public DefaultTreeModel
buildModel(final List
<Change
> changes
, final ChangeNodeDecorator changeNodeDecorator
) {
39 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
40 final ChangesGroupingPolicy policy
= createGroupingPolicy();
41 for (final Change change
: changes
) {
42 insertChangeNode(change
, foldersCache
, policy
, root
, new Computable
<ChangesBrowserNode
>() {
43 public ChangesBrowserNode
compute() {
44 return new ChangesBrowserChangeNode(myProject
, change
, changeNodeDecorator
);
49 collapseDirectories(model
, root
);
56 private ChangesGroupingPolicy
createGroupingPolicy() {
57 final ChangesGroupingPolicyFactory factory
= ChangesGroupingPolicyFactory
.getInstance(myProject
);
58 if (factory
!= null) {
59 return factory
.createGroupingPolicy(model
);
64 public DefaultTreeModel
buildModelFromFiles(final List
<VirtualFile
> files
) {
65 buildVirtualFiles(files
, null);
66 collapseDirectories(model
, root
);
71 public DefaultTreeModel
buildModelFromFilePaths(final Collection
<FilePath
> files
) {
72 buildFilePaths(files
, root
);
73 collapseDirectories(model
, root
);
78 private static class MyChangeNodeUnderChangeListDecorator
extends RemoteStatusChangeNodeDecorator
{
79 private final ChangeListRemoteState
.Reporter myReporter
;
81 private MyChangeNodeUnderChangeListDecorator(final RemoteRevisionsCache remoteRevisionsCache
, final ChangeListRemoteState
.Reporter reporter
) {
82 super(remoteRevisionsCache
);
83 myReporter
= reporter
;
87 protected void reportState(boolean state
) {
88 myReporter
.report(state
);
92 public DefaultTreeModel
buildModel(final List
<?
extends ChangeList
> changeLists
,
93 final List
<VirtualFile
> unversionedFiles
,
94 final List
<LocallyDeletedChange
> locallyDeletedFiles
,
95 final List
<VirtualFile
> modifiedWithoutEditing
,
96 final MultiMap
<String
, VirtualFile
> switchedFiles
,
97 @Nullable final List
<VirtualFile
> ignoredFiles
, @Nullable final List
<VirtualFile
> lockedFolders
,
98 @Nullable final Map
<VirtualFile
, LogicalLock
> logicallyLockedFiles
) {
99 final RemoteRevisionsCache revisionsCache
= RemoteRevisionsCache
.getInstance(myProject
);
100 for (ChangeList list
: changeLists
) {
101 final Collection
<Change
> changes
= list
.getChanges();
102 final ChangeListRemoteState listRemoteState
= new ChangeListRemoteState(changes
.size());
103 ChangesBrowserNode listNode
= new ChangesBrowserChangeListNode(myProject
, list
, listRemoteState
);
104 model
.insertNodeInto(listNode
, root
, 0);
105 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
106 final ChangesGroupingPolicy policy
= createGroupingPolicy();
108 for (final Change change
: changes
) {
109 final MyChangeNodeUnderChangeListDecorator decorator
=
110 new MyChangeNodeUnderChangeListDecorator(revisionsCache
, new ChangeListRemoteState
.Reporter(i
, listRemoteState
));
111 insertChangeNode(change
, foldersCache
, policy
, listNode
, new Computable
<ChangesBrowserNode
>() {
112 public ChangesBrowserNode
compute() {
113 return new ChangesBrowserChangeNode(myProject
, change
, decorator
);
120 if (!modifiedWithoutEditing
.isEmpty()) {
121 buildVirtualFiles(modifiedWithoutEditing
, ChangesBrowserNode
.MODIFIED_WITHOUT_EDITING_TAG
);
123 if (!unversionedFiles
.isEmpty()) {
124 buildVirtualFiles(unversionedFiles
, ChangesBrowserNode
.UNVERSIONED_FILES_TAG
);
126 if (!switchedFiles
.isEmpty()) {
127 buildSwitchedFiles(switchedFiles
);
129 if (ignoredFiles
!= null && !ignoredFiles
.isEmpty()) {
130 buildVirtualFiles(ignoredFiles
, ChangesBrowserNode
.IGNORED_FILES_TAG
);
132 if (lockedFolders
!= null && !lockedFolders
.isEmpty()) {
133 buildVirtualFiles(lockedFolders
, ChangesBrowserNode
.LOCKED_FOLDERS_TAG
);
135 if (logicallyLockedFiles
!= null && (! logicallyLockedFiles
.isEmpty())) {
136 buildLogicallyLockedFiles(logicallyLockedFiles
);
139 if (!locallyDeletedFiles
.isEmpty()) {
140 ChangesBrowserNode locallyDeletedNode
= ChangesBrowserNode
.create(myProject
, VcsBundle
.message("changes.nodetitle.locally.deleted.files"));
141 model
.insertNodeInto(locallyDeletedNode
, root
, root
.getChildCount());
142 buildLocallyDeletedPaths(locallyDeletedFiles
, locallyDeletedNode
);
145 collapseDirectories(model
, root
);
151 private void buildVirtualFiles(final Iterator
<FilePath
> iterator
, @Nullable final Object tag
) {
152 final ChangesBrowserNode baseNode
= createNode(tag
);
153 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
154 final ChangesGroupingPolicy policy
= createGroupingPolicy();
155 for (; ; iterator
.hasNext()) {
156 final FilePath path
= iterator
.next();
157 insertChangeNode(path
.getVirtualFile(), foldersCache
, policy
, baseNode
, defaultNodeCreator(path
.getVirtualFile()));
161 private void buildVirtualFiles(final List
<VirtualFile
> files
, @Nullable final Object tag
) {
162 final ChangesBrowserNode baseNode
= createNode(tag
);
163 insertFilesIntoNode(files
, baseNode
);
166 private ChangesBrowserNode
createNode(Object tag
) {
167 ChangesBrowserNode baseNode
;
169 baseNode
= ChangesBrowserNode
.create(myProject
, tag
);
170 model
.insertNodeInto(baseNode
, root
, root
.getChildCount());
178 private void insertFilesIntoNode(List
<VirtualFile
> files
, ChangesBrowserNode baseNode
) {
179 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
180 final ChangesGroupingPolicy policy
= createGroupingPolicy();
181 for (VirtualFile file
: files
) {
182 insertChangeNode(file
, foldersCache
, policy
, baseNode
, defaultNodeCreator(file
));
186 private void buildLocallyDeletedPaths(final Collection
<LocallyDeletedChange
> locallyDeletedChanges
, final ChangesBrowserNode baseNode
) {
187 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
188 final ChangesGroupingPolicy policy
= createGroupingPolicy();
189 for (LocallyDeletedChange change
: locallyDeletedChanges
) {
190 ChangesBrowserNode oldNode
= foldersCache
.get(change
.getPresentableUrl());
191 if (oldNode
== null) {
192 final ChangesBrowserNode node
= ChangesBrowserNode
.create(myProject
, change
);
193 final ChangesBrowserNode parent
= getParentNodeFor(node
, foldersCache
, policy
, baseNode
);
194 model
.insertNodeInto(node
, parent
, parent
.getChildCount());
195 foldersCache
.put(change
.getPresentableUrl(), node
);
200 private void buildFilePaths(final Collection
<FilePath
> filePaths
, final ChangesBrowserNode baseNode
) {
201 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
202 final ChangesGroupingPolicy policy
= createGroupingPolicy();
203 for (FilePath file
: filePaths
) {
205 ChangesBrowserNode oldNode
= foldersCache
.get(file
);
206 if (oldNode
== null) {
207 final ChangesBrowserNode node
= ChangesBrowserNode
.create(myProject
, file
);
208 model
.insertNodeInto(node
, getParentNodeFor(node
, foldersCache
, policy
, baseNode
), 0);
209 foldersCache
.put(file
.getIOFile().getAbsolutePath(), node
);
214 private void buildSwitchedFiles(final MultiMap
<String
, VirtualFile
> switchedFiles
) {
215 ChangesBrowserNode baseNode
= ChangesBrowserNode
.create(myProject
, ChangesBrowserNode
.SWITCHED_FILES_TAG
);
216 model
.insertNodeInto(baseNode
, root
, root
.getChildCount());
217 for(String branchName
: switchedFiles
.keySet()) {
218 final Collection
<VirtualFile
> switchedFileList
= switchedFiles
.get(branchName
);
219 if (switchedFileList
.size() > 0) {
220 ChangesBrowserNode branchNode
= ChangesBrowserNode
.create(myProject
, branchName
);
221 model
.insertNodeInto(branchNode
, baseNode
, baseNode
.getChildCount());
223 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
224 final ChangesGroupingPolicy policy
= createGroupingPolicy();
225 for (VirtualFile file
: switchedFileList
) {
226 insertChangeNode(file
, foldersCache
, policy
, branchNode
, defaultNodeCreator(file
));
232 private void buildLogicallyLockedFiles(final Map
<VirtualFile
, LogicalLock
> logicallyLockedFiles
) {
233 final ChangesBrowserNode baseNode
= createNode(ChangesBrowserNode
.LOGICALLY_LOCKED_TAG
);
235 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
236 final ChangesGroupingPolicy policy
= createGroupingPolicy();
237 for (Map
.Entry
<VirtualFile
, LogicalLock
> entry
: logicallyLockedFiles
.entrySet()) {
238 final VirtualFile file
= entry
.getKey();
239 final LogicalLock lock
= entry
.getValue();
240 final ChangesBrowserLogicallyLockedFile obj
= new ChangesBrowserLogicallyLockedFile(myProject
, file
, lock
);
241 insertChangeNode(obj
, foldersCache
, policy
, baseNode
,
242 defaultNodeCreator(obj
));
246 private Computable
<ChangesBrowserNode
> defaultNodeCreator(final Object change
) {
247 return new Computable
<ChangesBrowserNode
>() {
248 public ChangesBrowserNode
compute() {
249 return ChangesBrowserNode
.create(myProject
, change
);
254 private void insertChangeNode(final Object change
, final HashMap
<String
, ChangesBrowserNode
> foldersCache
,
255 final ChangesGroupingPolicy policy
,
256 final ChangesBrowserNode listNode
, final Computable
<ChangesBrowserNode
> nodeCreator
) {
257 final FilePath nodePath
= getPathForObject(change
);
258 ChangesBrowserNode oldNode
= (nodePath
== null) ?
null : foldersCache
.get(nodePath
.getIOFile().getAbsolutePath());
259 ChangesBrowserNode node
= nodeCreator
.compute();
260 if (oldNode
!= null) {
261 for(int i
=oldNode
.getChildCount()-1; i
>= 0; i
--) {
262 MutableTreeNode child
= (MutableTreeNode
) model
.getChild(oldNode
, i
);
263 model
.removeNodeFromParent(child
);
264 model
.insertNodeInto(child
, node
, model
.getChildCount(node
));
266 final MutableTreeNode parent
= (MutableTreeNode
)oldNode
.getParent();
267 int index
= model
.getIndexOfChild(parent
, oldNode
);
268 model
.removeNodeFromParent(oldNode
);
269 model
.insertNodeInto(node
, parent
, index
);
270 foldersCache
.put(nodePath
.getIOFile().getAbsolutePath(), node
);
273 ChangesBrowserNode parentNode
= getParentNodeFor(node
, foldersCache
, policy
, listNode
);
274 model
.insertNodeInto(node
, parentNode
, model
.getChildCount(parentNode
));
276 if (nodePath
!= null) {
277 foldersCache
.put(nodePath
.getIOFile().getAbsolutePath(), node
);
282 private void sortNodes() {
283 TreeUtil
.sort(model
, new Comparator() {
284 public int compare(final Object n1
, final Object n2
) {
285 final ChangesBrowserNode node1
= (ChangesBrowserNode
)n1
;
286 final ChangesBrowserNode node2
= (ChangesBrowserNode
)n2
;
288 final int classdiff
= node1
.getSortWeight() - node2
.getSortWeight();
289 if (classdiff
!= 0) return classdiff
;
291 return node1
.compareUserObjects(node2
.getUserObject());
295 model
.nodeStructureChanged((TreeNode
)model
.getRoot());
298 private static void collapseDirectories(DefaultTreeModel model
, ChangesBrowserNode node
) {
299 if (node
.getUserObject() instanceof FilePath
&& node
.getChildCount() == 1) {
300 final ChangesBrowserNode child
= (ChangesBrowserNode
)node
.getChildAt(0);
301 if (child
.getUserObject() instanceof FilePath
&& !child
.isLeaf()) {
302 ChangesBrowserNode parent
= (ChangesBrowserNode
)node
.getParent();
303 final int idx
= parent
.getIndex(node
);
304 model
.removeNodeFromParent(node
);
305 model
.removeNodeFromParent(child
);
306 model
.insertNodeInto(child
, parent
, idx
);
307 collapseDirectories(model
, parent
);
311 final Enumeration children
= node
.children();
312 while (children
.hasMoreElements()) {
313 ChangesBrowserNode child
= (ChangesBrowserNode
)children
.nextElement();
314 collapseDirectories(model
, child
);
319 public static FilePath
getPathForObject(Object o
) {
320 if (o
instanceof Change
) {
321 return ChangesUtil
.getFilePath((Change
)o
);
323 else if (o
instanceof VirtualFile
) {
324 return new FilePathImpl((VirtualFile
) o
);
326 else if (o
instanceof FilePath
) {
328 } else if (o
instanceof ChangesBrowserLogicallyLockedFile
) {
329 return new FilePathImpl(((ChangesBrowserLogicallyLockedFile
) o
).getUserObject());
330 } else if (o
instanceof LocallyDeletedChange
) {
331 return ((LocallyDeletedChange
) o
).getPath();
337 private ChangesBrowserNode
getParentNodeFor(ChangesBrowserNode node
,
338 Map
<String
, ChangesBrowserNode
> folderNodesCache
,
339 @Nullable ChangesGroupingPolicy policy
,
340 ChangesBrowserNode rootNode
) {
345 final FilePath path
= getPathForObject(node
.getUserObject());
347 if (policy
!= null) {
348 ChangesBrowserNode nodeFromPolicy
= policy
.getParentNodeFor(node
, rootNode
);
349 if (nodeFromPolicy
!= null) {
350 return nodeFromPolicy
;
354 FilePath parentPath
= path
.getParentPath();
355 if (parentPath
== null) {
359 ChangesBrowserNode parentNode
= folderNodesCache
.get(parentPath
.getIOFile().getAbsolutePath());
360 if (parentNode
== null) {
361 parentNode
= ChangesBrowserNode
.create(myProject
, parentPath
);
362 ChangesBrowserNode grandPa
= getParentNodeFor(parentNode
, folderNodesCache
, policy
, rootNode
);
363 model
.insertNodeInto(parentNode
, grandPa
, grandPa
.getChildCount());
364 folderNodesCache
.put(parentPath
.getIOFile().getAbsolutePath(), parentNode
);