VCS: file changed remotely hint
[fedora-idea.git] / vcs-impl / src / com / intellij / openapi / vcs / changes / ui / TreeModelBuilder.java
blob3a6c9671c35ce8d2bb5533ea6cd2f8c5c6896177
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;
18 import java.util.*;
20 /**
21 * @author max
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) {
32 myProject = project;
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);
46 });
49 collapseDirectories(model, root);
50 sortNodes();
52 return model;
55 @Nullable
56 private ChangesGroupingPolicy createGroupingPolicy() {
57 final ChangesGroupingPolicyFactory factory = ChangesGroupingPolicyFactory.getInstance(myProject);
58 if (factory != null) {
59 return factory.createGroupingPolicy(model);
61 return null;
64 public DefaultTreeModel buildModelFromFiles(final List<VirtualFile> files) {
65 buildVirtualFiles(files, null);
66 collapseDirectories(model, root);
67 sortNodes();
68 return model;
71 public DefaultTreeModel buildModelFromFilePaths(final Collection<FilePath> files) {
72 buildFilePaths(files, root);
73 collapseDirectories(model, root);
74 sortNodes();
75 return model;
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;
86 @Override
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();
107 int i = 0;
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);
116 ++ i;
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);
146 sortNodes();
148 return model;
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;
168 if (tag != null) {
169 baseNode = ChangesBrowserNode.create(myProject, tag);
170 model.insertNodeInto(baseNode, root, root.getChildCount());
172 else {
173 baseNode = root;
175 return baseNode;
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) {
204 assert file != null;
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);
272 else {
273 ChangesBrowserNode parentNode = getParentNodeFor(node, foldersCache, policy, listNode);
274 model.insertNodeInto(node, parentNode, model.getChildCount(parentNode));
275 // ?
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);
310 else {
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) {
327 return (FilePath)o;
328 } else if (o instanceof ChangesBrowserLogicallyLockedFile) {
329 return new FilePathImpl(((ChangesBrowserLogicallyLockedFile) o).getUserObject());
330 } else if (o instanceof LocallyDeletedChange) {
331 return ((LocallyDeletedChange) o).getPath();
334 return null;
337 private ChangesBrowserNode getParentNodeFor(ChangesBrowserNode node,
338 Map<String, ChangesBrowserNode> folderNodesCache,
339 @Nullable ChangesGroupingPolicy policy,
340 ChangesBrowserNode rootNode) {
341 if (showFlatten) {
342 return 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) {
356 return rootNode;
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);
367 return parentNode;