VCS: asynch load of committed changes when do "Browse changes"
[fedora-idea.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / ui / TreeModelBuilder.java
blob7908373b297c0f6a60d1dc20fc9abc846b121af7
1 /*
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.changes.ui;
18 import com.intellij.openapi.project.Project;
19 import com.intellij.openapi.util.Computable;
20 import com.intellij.openapi.util.Pair;
21 import com.intellij.openapi.vcs.FilePath;
22 import com.intellij.openapi.vcs.FilePathImpl;
23 import com.intellij.openapi.vcs.FileStatus;
24 import com.intellij.openapi.vcs.VcsBundle;
25 import com.intellij.openapi.vcs.changes.*;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.ui.SimpleColoredComponent;
28 import com.intellij.ui.SimpleTextAttributes;
29 import com.intellij.util.containers.MultiMap;
30 import com.intellij.util.ui.tree.TreeUtil;
31 import org.jetbrains.annotations.NonNls;
32 import org.jetbrains.annotations.Nullable;
34 import javax.swing.tree.DefaultTreeModel;
35 import javax.swing.tree.MutableTreeNode;
36 import javax.swing.tree.TreeNode;
37 import java.util.*;
39 /**
40 * @author max
42 public class TreeModelBuilder {
43 @NonNls public static final String ROOT_NODE_VALUE = "root";
45 private final Project myProject;
46 private final boolean showFlatten;
47 private DefaultTreeModel model;
48 private final ChangesBrowserNode root;
49 private boolean myPolicyInitialized;
50 private ChangesGroupingPolicy myPolicy;
51 private final HashMap<String, ChangesBrowserNode> myFoldersCache;
53 public TreeModelBuilder(final Project project, final boolean showFlatten) {
54 myProject = project;
55 this.showFlatten = showFlatten;
56 root = ChangesBrowserNode.create(myProject, ROOT_NODE_VALUE);
57 model = new DefaultTreeModel(root);
58 myFoldersCache = new HashMap<String, ChangesBrowserNode>();
61 public DefaultTreeModel buildModel(final List<Change> changes, final ChangeNodeDecorator changeNodeDecorator) {
62 final ChangesGroupingPolicy policy = createGroupingPolicy();
63 for (final Change change : changes) {
64 insertChangeNode(change, policy, root, new Computable<ChangesBrowserNode>() {
65 public ChangesBrowserNode compute() {
66 return new ChangesBrowserChangeNode(myProject, change, changeNodeDecorator);
68 });
71 collapseDirectories(model, root);
72 sortNodes();
74 return model;
77 @Nullable
78 private ChangesGroupingPolicy createGroupingPolicy() {
79 if (! myPolicyInitialized) {
80 myPolicyInitialized = true;
81 final ChangesGroupingPolicyFactory factory = ChangesGroupingPolicyFactory.getInstance(myProject);
82 if (factory != null) {
83 myPolicy = factory.createGroupingPolicy(model);
86 return myPolicy;
89 public DefaultTreeModel buildModelFromFiles(final List<VirtualFile> files) {
90 buildVirtualFiles(files, null);
91 collapseDirectories(model, root);
92 sortNodes();
93 return model;
96 public DefaultTreeModel buildModelFromFilePaths(final Collection<FilePath> files) {
97 buildFilePaths(files, root);
98 collapseDirectories(model, root);
99 sortNodes();
100 return model;
103 private static class MyChangeNodeUnderChangeListDecorator extends RemoteStatusChangeNodeDecorator {
104 private final ChangeListRemoteState.Reporter myReporter;
106 private MyChangeNodeUnderChangeListDecorator(final RemoteRevisionsCache remoteRevisionsCache, final ChangeListRemoteState.Reporter reporter) {
107 super(remoteRevisionsCache);
108 myReporter = reporter;
111 @Override
112 protected void reportState(boolean state) {
113 myReporter.report(state);
116 public void preDecorate(Change change, ChangesBrowserNodeRenderer renderer, boolean showFlatten) {
120 public DefaultTreeModel buildModel(final List<? extends ChangeList> changeLists,
121 final List<VirtualFile> unversionedFiles,
122 final List<LocallyDeletedChange> locallyDeletedFiles,
123 final List<VirtualFile> modifiedWithoutEditing,
124 final MultiMap<String, VirtualFile> switchedFiles,
125 @Nullable Map<VirtualFile, String> switchedRoots,
126 @Nullable final List<VirtualFile> ignoredFiles, @Nullable final List<VirtualFile> lockedFolders,
127 @Nullable final Map<VirtualFile, LogicalLock> logicallyLockedFiles) {
128 buildModel(changeLists);
130 if (!modifiedWithoutEditing.isEmpty()) {
131 buildVirtualFiles(modifiedWithoutEditing, ChangesBrowserNode.MODIFIED_WITHOUT_EDITING_TAG);
133 if (!unversionedFiles.isEmpty()) {
134 buildVirtualFiles(unversionedFiles, ChangesBrowserNode.UNVERSIONED_FILES_TAG);
136 if (switchedRoots != null && (! switchedRoots.isEmpty())) {
137 buildSwitchedRoots(switchedRoots);
139 if (!switchedFiles.isEmpty()) {
140 buildSwitchedFiles(switchedFiles);
142 if (ignoredFiles != null && !ignoredFiles.isEmpty()) {
143 buildVirtualFiles(ignoredFiles, ChangesBrowserNode.IGNORED_FILES_TAG);
145 if (lockedFolders != null && !lockedFolders.isEmpty()) {
146 buildVirtualFiles(lockedFolders, ChangesBrowserNode.LOCKED_FOLDERS_TAG);
148 if (logicallyLockedFiles != null && (! logicallyLockedFiles.isEmpty())) {
149 buildLogicallyLockedFiles(logicallyLockedFiles);
152 if (!locallyDeletedFiles.isEmpty()) {
153 ChangesBrowserNode locallyDeletedNode = ChangesBrowserNode.create(myProject, VcsBundle.message("changes.nodetitle.locally.deleted.files"));
154 model.insertNodeInto(locallyDeletedNode, root, root.getChildCount());
155 buildLocallyDeletedPaths(locallyDeletedFiles, locallyDeletedNode);
158 collapseDirectories(model, root);
159 sortNodes();
161 return model;
164 public DefaultTreeModel buildModel(List<? extends ChangeList> changeLists) {
165 final RemoteRevisionsCache revisionsCache = RemoteRevisionsCache.getInstance(myProject);
166 for (ChangeList list : changeLists) {
167 final Collection<Change> changes = list.getChanges();
168 final ChangeListRemoteState listRemoteState = new ChangeListRemoteState(changes.size());
169 ChangesBrowserNode listNode = new ChangesBrowserChangeListNode(myProject, list, listRemoteState);
170 model.insertNodeInto(listNode, root, 0);
171 final ChangesGroupingPolicy policy = createGroupingPolicy();
172 int i = 0;
173 for (final Change change : changes) {
174 final MyChangeNodeUnderChangeListDecorator decorator =
175 new MyChangeNodeUnderChangeListDecorator(revisionsCache, new ChangeListRemoteState.Reporter(i, listRemoteState));
176 insertChangeNode(change, policy, listNode, new Computable<ChangesBrowserNode>() {
177 public ChangesBrowserNode compute() {
178 return new ChangesBrowserChangeNode(myProject, change, decorator);
181 ++ i;
184 return model;
187 private void buildVirtualFiles(final Iterator<FilePath> iterator, @Nullable final Object tag) {
188 final ChangesBrowserNode baseNode = createNode(tag);
189 final ChangesGroupingPolicy policy = createGroupingPolicy();
190 for (; ; iterator.hasNext()) {
191 final FilePath path = iterator.next();
192 insertChangeNode(path.getVirtualFile(), policy, baseNode, defaultNodeCreator(path.getVirtualFile()));
196 private void buildVirtualFiles(final List<VirtualFile> files, @Nullable final Object tag) {
197 final ChangesBrowserNode baseNode = createNode(tag);
198 insertFilesIntoNode(files, baseNode);
201 private ChangesBrowserNode createNode(Object tag) {
202 ChangesBrowserNode baseNode;
203 if (tag != null) {
204 baseNode = ChangesBrowserNode.create(myProject, tag);
205 model.insertNodeInto(baseNode, root, root.getChildCount());
207 else {
208 baseNode = root;
210 return baseNode;
213 private void insertFilesIntoNode(List<VirtualFile> files, ChangesBrowserNode baseNode) {
214 final ChangesGroupingPolicy policy = createGroupingPolicy();
215 for (VirtualFile file : files) {
216 insertChangeNode(file, policy, baseNode, defaultNodeCreator(file));
220 private void buildLocallyDeletedPaths(final Collection<LocallyDeletedChange> locallyDeletedChanges, final ChangesBrowserNode baseNode) {
221 final ChangesGroupingPolicy policy = createGroupingPolicy();
222 for (LocallyDeletedChange change : locallyDeletedChanges) {
223 ChangesBrowserNode oldNode = myFoldersCache.get(change.getPresentableUrl());
224 if (oldNode == null) {
225 final ChangesBrowserNode node = ChangesBrowserNode.create(myProject, change);
226 final ChangesBrowserNode parent = getParentNodeFor(node, policy, baseNode);
227 model.insertNodeInto(node, parent, parent.getChildCount());
228 myFoldersCache.put(change.getPresentableUrl(), node);
233 public void buildFilePaths(final Collection<FilePath> filePaths, final ChangesBrowserNode baseNode) {
234 final ChangesGroupingPolicy policy = createGroupingPolicy();
235 for (FilePath file : filePaths) {
236 assert file != null;
237 ChangesBrowserNode oldNode = myFoldersCache.get(file.getIOFile().getAbsolutePath());
238 if (oldNode == null) {
239 final ChangesBrowserNode node = ChangesBrowserNode.create(myProject, file);
240 final ChangesBrowserNode parentNode = getParentNodeFor(node, policy, baseNode);
241 model.insertNodeInto(node, parentNode, 0);
242 myFoldersCache.put(file.getIOFile().getAbsolutePath(), node);
247 private void buildSwitchedRoots(final Map<VirtualFile, String> switchedRoots) {
248 final ChangesBrowserNode rootsHeadNode = ChangesBrowserNode.create(myProject, ChangesBrowserNode.SWITCHED_ROOTS_TAG);
249 rootsHeadNode.setAttributes(SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES);
250 model.insertNodeInto(rootsHeadNode, root, root.getChildCount());
252 for (VirtualFile vf : switchedRoots.keySet()) {
253 final ChangesGroupingPolicy policy = createGroupingPolicy();
254 final ContentRevision cr = new CurrentContentRevision(new FilePathImpl(vf));
255 final Change change = new Change(cr, cr, FileStatus.NOT_CHANGED);
256 final String branchName = switchedRoots.get(vf);
257 insertChangeNode(vf, policy, rootsHeadNode, new Computable<ChangesBrowserNode>() {
258 public ChangesBrowserNode compute() {
259 return new ChangesBrowserChangeNode(myProject, change, new ChangeNodeDecorator() {
260 public void decorate(Change change, SimpleColoredComponent component, boolean isShowFlatten) {
262 public List<Pair<String, Stress>> stressPartsOfFileName(Change change, String parentPath) {
263 return null;
265 public void preDecorate(Change change, ChangesBrowserNodeRenderer renderer, boolean showFlatten) {
266 renderer.append("[" + branchName + "] ", SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES);
274 private void buildSwitchedFiles(final MultiMap<String, VirtualFile> switchedFiles) {
275 ChangesBrowserNode baseNode = ChangesBrowserNode.create(myProject, ChangesBrowserNode.SWITCHED_FILES_TAG);
276 model.insertNodeInto(baseNode, root, root.getChildCount());
277 for(String branchName: switchedFiles.keySet()) {
278 final Collection<VirtualFile> switchedFileList = switchedFiles.get(branchName);
279 if (switchedFileList.size() > 0) {
280 ChangesBrowserNode branchNode = ChangesBrowserNode.create(myProject, branchName);
281 model.insertNodeInto(branchNode, baseNode, baseNode.getChildCount());
283 final ChangesGroupingPolicy policy = createGroupingPolicy();
284 for (VirtualFile file : switchedFileList) {
285 insertChangeNode(file, policy, branchNode, defaultNodeCreator(file));
291 private void buildLogicallyLockedFiles(final Map<VirtualFile, LogicalLock> logicallyLockedFiles) {
292 final ChangesBrowserNode baseNode = createNode(ChangesBrowserNode.LOGICALLY_LOCKED_TAG);
294 final ChangesGroupingPolicy policy = createGroupingPolicy();
295 for (Map.Entry<VirtualFile, LogicalLock> entry : logicallyLockedFiles.entrySet()) {
296 final VirtualFile file = entry.getKey();
297 final LogicalLock lock = entry.getValue();
298 final ChangesBrowserLogicallyLockedFile obj = new ChangesBrowserLogicallyLockedFile(myProject, file, lock);
299 insertChangeNode(obj, policy, baseNode,
300 defaultNodeCreator(obj));
304 private Computable<ChangesBrowserNode> defaultNodeCreator(final Object change) {
305 return new Computable<ChangesBrowserNode>() {
306 public ChangesBrowserNode compute() {
307 return ChangesBrowserNode.create(myProject, change);
312 private void insertChangeNode(final Object change, final ChangesGroupingPolicy policy,
313 final ChangesBrowserNode listNode, final Computable<ChangesBrowserNode> nodeCreator) {
314 final FilePath nodePath = getPathForObject(change);
315 ChangesBrowserNode oldNode = (nodePath == null) ? null : myFoldersCache.get(nodePath.getIOFile().getAbsolutePath());
316 ChangesBrowserNode node = nodeCreator.compute();
317 if (oldNode != null) {
318 for(int i=oldNode.getChildCount()-1; i >= 0; i--) {
319 MutableTreeNode child = (MutableTreeNode) model.getChild(oldNode, i);
320 model.removeNodeFromParent(child);
321 model.insertNodeInto(child, node, model.getChildCount(node));
323 final MutableTreeNode parent = (MutableTreeNode)oldNode.getParent();
324 int index = model.getIndexOfChild(parent, oldNode);
325 model.removeNodeFromParent(oldNode);
326 model.insertNodeInto(node, parent, index);
327 myFoldersCache.put(nodePath.getIOFile().getAbsolutePath(), node);
329 else {
330 ChangesBrowserNode parentNode = getParentNodeFor(node, policy, listNode);
331 model.insertNodeInto(node, parentNode, model.getChildCount(parentNode));
332 // ?
333 if (nodePath != null) {
334 myFoldersCache.put(nodePath.getIOFile().getAbsolutePath(), node);
339 private void sortNodes() {
340 TreeUtil.sort(model, new Comparator() {
341 public int compare(final Object n1, final Object n2) {
342 final ChangesBrowserNode node1 = (ChangesBrowserNode)n1;
343 final ChangesBrowserNode node2 = (ChangesBrowserNode)n2;
345 final int classdiff = node1.getSortWeight() - node2.getSortWeight();
346 if (classdiff != 0) return classdiff;
348 return node1.compareUserObjects(node2.getUserObject());
352 model.nodeStructureChanged((TreeNode)model.getRoot());
355 private static void collapseDirectories(DefaultTreeModel model, ChangesBrowserNode node) {
356 if (node.getUserObject() instanceof FilePath && node.getChildCount() == 1) {
357 final ChangesBrowserNode child = (ChangesBrowserNode)node.getChildAt(0);
358 if (child.getUserObject() instanceof FilePath && !child.isLeaf()) {
359 ChangesBrowserNode parent = (ChangesBrowserNode)node.getParent();
360 final int idx = parent.getIndex(node);
361 model.removeNodeFromParent(node);
362 model.removeNodeFromParent(child);
363 model.insertNodeInto(child, parent, idx);
364 collapseDirectories(model, parent);
367 else {
368 final Enumeration children = node.children();
369 while (children.hasMoreElements()) {
370 ChangesBrowserNode child = (ChangesBrowserNode)children.nextElement();
371 collapseDirectories(model, child);
376 public static FilePath getPathForObject(Object o) {
377 if (o instanceof Change) {
378 return ChangesUtil.getFilePath((Change)o);
380 else if (o instanceof VirtualFile) {
381 return new FilePathImpl((VirtualFile) o);
383 else if (o instanceof FilePath) {
384 return (FilePath)o;
385 } else if (o instanceof ChangesBrowserLogicallyLockedFile) {
386 return new FilePathImpl(((ChangesBrowserLogicallyLockedFile) o).getUserObject());
387 } else if (o instanceof LocallyDeletedChange) {
388 return ((LocallyDeletedChange) o).getPath();
391 return null;
394 private ChangesBrowserNode getParentNodeFor(ChangesBrowserNode node, @Nullable ChangesGroupingPolicy policy, ChangesBrowserNode rootNode) {
395 if (showFlatten) {
396 return rootNode;
399 final FilePath path = getPathForObject(node.getUserObject());
401 if (policy != null) {
402 ChangesBrowserNode nodeFromPolicy = policy.getParentNodeFor(node, rootNode);
403 if (nodeFromPolicy != null) {
404 return nodeFromPolicy;
408 FilePath parentPath = path.getParentPath();
409 if (parentPath == null) {
410 return rootNode;
413 final String parentKey = parentPath.getIOFile().getAbsolutePath();
414 ChangesBrowserNode parentNode = myFoldersCache.get(parentKey);
415 if (parentNode == null) {
416 parentNode = ChangesBrowserNode.create(myProject, parentPath);
417 ChangesBrowserNode grandPa = getParentNodeFor(parentNode, policy, rootNode);
418 model.insertNodeInto(parentNode, grandPa, grandPa.getChildCount());
419 myFoldersCache.put(parentKey, parentNode);
422 return parentNode;
425 public DefaultTreeModel clearAndGetModel() {
426 root.removeAllChildren();
427 model = new DefaultTreeModel(root);
428 myFoldersCache.clear();
429 myPolicyInitialized = false;
430 return model;
433 public boolean isEmpty() {
434 return model.getChildCount(root) == 0;