ef1b6e109818c190403093109386cdf1aab98f57
[fedora-idea.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / ui / TreeModelBuilder.java
blobef1b6e109818c190403093109386cdf1aab98f57
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 final DefaultTreeModel model;
48 private final ChangesBrowserNode root;
50 public TreeModelBuilder(final Project project, final boolean showFlatten) {
51 myProject = project;
52 this.showFlatten = showFlatten;
53 root = ChangesBrowserNode.create(myProject, ROOT_NODE_VALUE);
54 model = new DefaultTreeModel(root);
57 public DefaultTreeModel buildModel(final List<Change> changes, final ChangeNodeDecorator changeNodeDecorator) {
58 final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
59 final ChangesGroupingPolicy policy = createGroupingPolicy();
60 for (final Change change : changes) {
61 insertChangeNode(change, foldersCache, policy, root, new Computable<ChangesBrowserNode>() {
62 public ChangesBrowserNode compute() {
63 return new ChangesBrowserChangeNode(myProject, change, changeNodeDecorator);
65 });
68 collapseDirectories(model, root);
69 sortNodes();
71 return model;
74 @Nullable
75 private ChangesGroupingPolicy createGroupingPolicy() {
76 final ChangesGroupingPolicyFactory factory = ChangesGroupingPolicyFactory.getInstance(myProject);
77 if (factory != null) {
78 return factory.createGroupingPolicy(model);
80 return null;
83 public DefaultTreeModel buildModelFromFiles(final List<VirtualFile> files) {
84 buildVirtualFiles(files, null);
85 collapseDirectories(model, root);
86 sortNodes();
87 return model;
90 public DefaultTreeModel buildModelFromFilePaths(final Collection<FilePath> files) {
91 buildFilePaths(files, root);
92 collapseDirectories(model, root);
93 sortNodes();
94 return model;
97 private static class MyChangeNodeUnderChangeListDecorator extends RemoteStatusChangeNodeDecorator {
98 private final ChangeListRemoteState.Reporter myReporter;
100 private MyChangeNodeUnderChangeListDecorator(final RemoteRevisionsCache remoteRevisionsCache, final ChangeListRemoteState.Reporter reporter) {
101 super(remoteRevisionsCache);
102 myReporter = reporter;
105 @Override
106 protected void reportState(boolean state) {
107 myReporter.report(state);
110 public void preDecorate(Change change, ChangesBrowserNodeRenderer renderer, boolean showFlatten) {
114 public DefaultTreeModel buildModel(final List<? extends ChangeList> changeLists,
115 final List<VirtualFile> unversionedFiles,
116 final List<LocallyDeletedChange> locallyDeletedFiles,
117 final List<VirtualFile> modifiedWithoutEditing,
118 final MultiMap<String, VirtualFile> switchedFiles,
119 @Nullable Map<VirtualFile, String> switchedRoots,
120 @Nullable final List<VirtualFile> ignoredFiles, @Nullable final List<VirtualFile> lockedFolders,
121 @Nullable final Map<VirtualFile, LogicalLock> logicallyLockedFiles) {
122 buildModel(changeLists);
124 if (!modifiedWithoutEditing.isEmpty()) {
125 buildVirtualFiles(modifiedWithoutEditing, ChangesBrowserNode.MODIFIED_WITHOUT_EDITING_TAG);
127 if (!unversionedFiles.isEmpty()) {
128 buildVirtualFiles(unversionedFiles, ChangesBrowserNode.UNVERSIONED_FILES_TAG);
130 if (switchedRoots != null && (! switchedRoots.isEmpty())) {
131 buildSwitchedRoots(switchedRoots);
133 if (!switchedFiles.isEmpty()) {
134 buildSwitchedFiles(switchedFiles);
136 if (ignoredFiles != null && !ignoredFiles.isEmpty()) {
137 buildVirtualFiles(ignoredFiles, ChangesBrowserNode.IGNORED_FILES_TAG);
139 if (lockedFolders != null && !lockedFolders.isEmpty()) {
140 buildVirtualFiles(lockedFolders, ChangesBrowserNode.LOCKED_FOLDERS_TAG);
142 if (logicallyLockedFiles != null && (! logicallyLockedFiles.isEmpty())) {
143 buildLogicallyLockedFiles(logicallyLockedFiles);
146 if (!locallyDeletedFiles.isEmpty()) {
147 ChangesBrowserNode locallyDeletedNode = ChangesBrowserNode.create(myProject, VcsBundle.message("changes.nodetitle.locally.deleted.files"));
148 model.insertNodeInto(locallyDeletedNode, root, root.getChildCount());
149 buildLocallyDeletedPaths(locallyDeletedFiles, locallyDeletedNode);
152 collapseDirectories(model, root);
153 sortNodes();
155 return model;
158 public DefaultTreeModel buildModel(List<? extends ChangeList> changeLists) {
159 final RemoteRevisionsCache revisionsCache = RemoteRevisionsCache.getInstance(myProject);
160 for (ChangeList list : changeLists) {
161 final Collection<Change> changes = list.getChanges();
162 final ChangeListRemoteState listRemoteState = new ChangeListRemoteState(changes.size());
163 ChangesBrowserNode listNode = new ChangesBrowserChangeListNode(myProject, list, listRemoteState);
164 model.insertNodeInto(listNode, root, 0);
165 final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
166 final ChangesGroupingPolicy policy = createGroupingPolicy();
167 int i = 0;
168 for (final Change change : changes) {
169 final MyChangeNodeUnderChangeListDecorator decorator =
170 new MyChangeNodeUnderChangeListDecorator(revisionsCache, new ChangeListRemoteState.Reporter(i, listRemoteState));
171 insertChangeNode(change, foldersCache, policy, listNode, new Computable<ChangesBrowserNode>() {
172 public ChangesBrowserNode compute() {
173 return new ChangesBrowserChangeNode(myProject, change, decorator);
176 ++ i;
179 return model;
182 private void buildVirtualFiles(final Iterator<FilePath> iterator, @Nullable final Object tag) {
183 final ChangesBrowserNode baseNode = createNode(tag);
184 final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
185 final ChangesGroupingPolicy policy = createGroupingPolicy();
186 for (; ; iterator.hasNext()) {
187 final FilePath path = iterator.next();
188 insertChangeNode(path.getVirtualFile(), foldersCache, policy, baseNode, defaultNodeCreator(path.getVirtualFile()));
192 private void buildVirtualFiles(final List<VirtualFile> files, @Nullable final Object tag) {
193 final ChangesBrowserNode baseNode = createNode(tag);
194 insertFilesIntoNode(files, baseNode);
197 private ChangesBrowserNode createNode(Object tag) {
198 ChangesBrowserNode baseNode;
199 if (tag != null) {
200 baseNode = ChangesBrowserNode.create(myProject, tag);
201 model.insertNodeInto(baseNode, root, root.getChildCount());
203 else {
204 baseNode = root;
206 return baseNode;
209 private void insertFilesIntoNode(List<VirtualFile> files, ChangesBrowserNode baseNode) {
210 final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
211 final ChangesGroupingPolicy policy = createGroupingPolicy();
212 for (VirtualFile file : files) {
213 insertChangeNode(file, foldersCache, policy, baseNode, defaultNodeCreator(file));
217 private void buildLocallyDeletedPaths(final Collection<LocallyDeletedChange> locallyDeletedChanges, final ChangesBrowserNode baseNode) {
218 final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
219 final ChangesGroupingPolicy policy = createGroupingPolicy();
220 for (LocallyDeletedChange change : locallyDeletedChanges) {
221 ChangesBrowserNode oldNode = foldersCache.get(change.getPresentableUrl());
222 if (oldNode == null) {
223 final ChangesBrowserNode node = ChangesBrowserNode.create(myProject, change);
224 final ChangesBrowserNode parent = getParentNodeFor(node, foldersCache, policy, baseNode);
225 model.insertNodeInto(node, parent, parent.getChildCount());
226 foldersCache.put(change.getPresentableUrl(), node);
231 private void buildFilePaths(final Collection<FilePath> filePaths, final ChangesBrowserNode baseNode) {
232 final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
233 final ChangesGroupingPolicy policy = createGroupingPolicy();
234 for (FilePath file : filePaths) {
235 assert file != null;
236 ChangesBrowserNode oldNode = foldersCache.get(file);
237 if (oldNode == null) {
238 final ChangesBrowserNode node = ChangesBrowserNode.create(myProject, file);
239 model.insertNodeInto(node, getParentNodeFor(node, foldersCache, policy, baseNode), 0);
240 foldersCache.put(file.getIOFile().getAbsolutePath(), node);
245 private void buildSwitchedRoots(final Map<VirtualFile, String> switchedRoots) {
246 final ChangesBrowserNode rootsHeadNode = ChangesBrowserNode.create(myProject, ChangesBrowserNode.SWITCHED_ROOTS_TAG);
247 rootsHeadNode.setAttributes(SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES);
248 model.insertNodeInto(rootsHeadNode, root, root.getChildCount());
250 for (VirtualFile vf : switchedRoots.keySet()) {
251 final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
252 final ChangesGroupingPolicy policy = createGroupingPolicy();
253 final ContentRevision cr = new CurrentContentRevision(new FilePathImpl(vf));
254 final Change change = new Change(cr, cr, FileStatus.NOT_CHANGED);
255 final String branchName = switchedRoots.get(vf);
256 insertChangeNode(vf, foldersCache, policy, rootsHeadNode, new Computable<ChangesBrowserNode>() {
257 public ChangesBrowserNode compute() {
258 return new ChangesBrowserChangeNode(myProject, change, new ChangeNodeDecorator() {
259 public void decorate(Change change, SimpleColoredComponent component, boolean isShowFlatten) {
261 public List<Pair<String, Stress>> stressPartsOfFileName(Change change, String parentPath) {
262 return null;
264 public void preDecorate(Change change, ChangesBrowserNodeRenderer renderer, boolean showFlatten) {
265 renderer.append("[" + branchName + "] ", SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES);
273 private void buildSwitchedFiles(final MultiMap<String, VirtualFile> switchedFiles) {
274 ChangesBrowserNode baseNode = ChangesBrowserNode.create(myProject, ChangesBrowserNode.SWITCHED_FILES_TAG);
275 model.insertNodeInto(baseNode, root, root.getChildCount());
276 for(String branchName: switchedFiles.keySet()) {
277 final Collection<VirtualFile> switchedFileList = switchedFiles.get(branchName);
278 if (switchedFileList.size() > 0) {
279 ChangesBrowserNode branchNode = ChangesBrowserNode.create(myProject, branchName);
280 model.insertNodeInto(branchNode, baseNode, baseNode.getChildCount());
282 final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
283 final ChangesGroupingPolicy policy = createGroupingPolicy();
284 for (VirtualFile file : switchedFileList) {
285 insertChangeNode(file, foldersCache, policy, branchNode, defaultNodeCreator(file));
291 private void buildLogicallyLockedFiles(final Map<VirtualFile, LogicalLock> logicallyLockedFiles) {
292 final ChangesBrowserNode baseNode = createNode(ChangesBrowserNode.LOGICALLY_LOCKED_TAG);
294 final HashMap<String, ChangesBrowserNode> foldersCache = new HashMap<String, ChangesBrowserNode>();
295 final ChangesGroupingPolicy policy = createGroupingPolicy();
296 for (Map.Entry<VirtualFile, LogicalLock> entry : logicallyLockedFiles.entrySet()) {
297 final VirtualFile file = entry.getKey();
298 final LogicalLock lock = entry.getValue();
299 final ChangesBrowserLogicallyLockedFile obj = new ChangesBrowserLogicallyLockedFile(myProject, file, lock);
300 insertChangeNode(obj, foldersCache, policy, baseNode,
301 defaultNodeCreator(obj));
305 private Computable<ChangesBrowserNode> defaultNodeCreator(final Object change) {
306 return new Computable<ChangesBrowserNode>() {
307 public ChangesBrowserNode compute() {
308 return ChangesBrowserNode.create(myProject, change);
313 private void insertChangeNode(final Object change, final HashMap<String, ChangesBrowserNode> foldersCache,
314 final ChangesGroupingPolicy policy,
315 final ChangesBrowserNode listNode, final Computable<ChangesBrowserNode> nodeCreator) {
316 final FilePath nodePath = getPathForObject(change);
317 ChangesBrowserNode oldNode = (nodePath == null) ? null : foldersCache.get(nodePath.getIOFile().getAbsolutePath());
318 ChangesBrowserNode node = nodeCreator.compute();
319 if (oldNode != null) {
320 for(int i=oldNode.getChildCount()-1; i >= 0; i--) {
321 MutableTreeNode child = (MutableTreeNode) model.getChild(oldNode, i);
322 model.removeNodeFromParent(child);
323 model.insertNodeInto(child, node, model.getChildCount(node));
325 final MutableTreeNode parent = (MutableTreeNode)oldNode.getParent();
326 int index = model.getIndexOfChild(parent, oldNode);
327 model.removeNodeFromParent(oldNode);
328 model.insertNodeInto(node, parent, index);
329 foldersCache.put(nodePath.getIOFile().getAbsolutePath(), node);
331 else {
332 ChangesBrowserNode parentNode = getParentNodeFor(node, foldersCache, policy, listNode);
333 model.insertNodeInto(node, parentNode, model.getChildCount(parentNode));
334 // ?
335 if (nodePath != null) {
336 foldersCache.put(nodePath.getIOFile().getAbsolutePath(), node);
341 private void sortNodes() {
342 TreeUtil.sort(model, new Comparator() {
343 public int compare(final Object n1, final Object n2) {
344 final ChangesBrowserNode node1 = (ChangesBrowserNode)n1;
345 final ChangesBrowserNode node2 = (ChangesBrowserNode)n2;
347 final int classdiff = node1.getSortWeight() - node2.getSortWeight();
348 if (classdiff != 0) return classdiff;
350 return node1.compareUserObjects(node2.getUserObject());
354 model.nodeStructureChanged((TreeNode)model.getRoot());
357 private static void collapseDirectories(DefaultTreeModel model, ChangesBrowserNode node) {
358 if (node.getUserObject() instanceof FilePath && node.getChildCount() == 1) {
359 final ChangesBrowserNode child = (ChangesBrowserNode)node.getChildAt(0);
360 if (child.getUserObject() instanceof FilePath && !child.isLeaf()) {
361 ChangesBrowserNode parent = (ChangesBrowserNode)node.getParent();
362 final int idx = parent.getIndex(node);
363 model.removeNodeFromParent(node);
364 model.removeNodeFromParent(child);
365 model.insertNodeInto(child, parent, idx);
366 collapseDirectories(model, parent);
369 else {
370 final Enumeration children = node.children();
371 while (children.hasMoreElements()) {
372 ChangesBrowserNode child = (ChangesBrowserNode)children.nextElement();
373 collapseDirectories(model, child);
378 public static FilePath getPathForObject(Object o) {
379 if (o instanceof Change) {
380 return ChangesUtil.getFilePath((Change)o);
382 else if (o instanceof VirtualFile) {
383 return new FilePathImpl((VirtualFile) o);
385 else if (o instanceof FilePath) {
386 return (FilePath)o;
387 } else if (o instanceof ChangesBrowserLogicallyLockedFile) {
388 return new FilePathImpl(((ChangesBrowserLogicallyLockedFile) o).getUserObject());
389 } else if (o instanceof LocallyDeletedChange) {
390 return ((LocallyDeletedChange) o).getPath();
393 return null;
396 private ChangesBrowserNode getParentNodeFor(ChangesBrowserNode node,
397 Map<String, ChangesBrowserNode> folderNodesCache,
398 @Nullable ChangesGroupingPolicy policy,
399 ChangesBrowserNode rootNode) {
400 if (showFlatten) {
401 return rootNode;
404 final FilePath path = getPathForObject(node.getUserObject());
406 if (policy != null) {
407 ChangesBrowserNode nodeFromPolicy = policy.getParentNodeFor(node, rootNode);
408 if (nodeFromPolicy != null) {
409 return nodeFromPolicy;
413 FilePath parentPath = path.getParentPath();
414 if (parentPath == null) {
415 return rootNode;
418 ChangesBrowserNode parentNode = folderNodesCache.get(parentPath.getIOFile().getAbsolutePath());
419 if (parentNode == null) {
420 parentNode = ChangesBrowserNode.create(myProject, parentPath);
421 ChangesBrowserNode grandPa = getParentNodeFor(parentNode, folderNodesCache, policy, rootNode);
422 model.insertNodeInto(parentNode, grandPa, grandPa.getChildCount());
423 folderNodesCache.put(parentPath.getIOFile().getAbsolutePath(), parentNode);
426 return parentNode;