/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.vcs.changes.ui;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangeListOwner;
import com.intellij.openapi.vcs.changes.LocallyDeletedChange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.ColoredTreeCellRenderer;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Convertor;
import org.jetbrains.annotations.NotNull;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreePath;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static com.intellij.util.FontUtil.spaceAndThinSpace;
public class ChangesBrowserNode<T> extends DefaultMutableTreeNode {
public static final Object IGNORED_FILES_TAG = new Tag("changes.nodetitle.ignored.files");
public static final Object LOCKED_FOLDERS_TAG = new Tag("changes.nodetitle.locked.folders");
public static final Object LOGICALLY_LOCKED_TAG = new Tag("changes.nodetitle.logicallt.locked.folders");
public static final Object UNVERSIONED_FILES_TAG = new Tag("changes.nodetitle.unversioned.files");
public static final Object MODIFIED_WITHOUT_EDITING_TAG = new Tag("changes.nodetitle.modified.without.editing");
public static final Object SWITCHED_FILES_TAG = new Tag("changes.nodetitle.switched.files");
public static final Object SWITCHED_ROOTS_TAG = new Tag("changes.nodetitle.switched.roots");
public static final Object LOCALLY_DELETED_NODE_TAG = new Tag("changes.nodetitle.locally.deleted.files");
protected static final int DEFAULT_CHANGE_LIST_SORT_WEIGHT = 1;
protected static final int CHANGE_LIST_SORT_WEIGHT = 2;
protected static final int MODULE_SORT_WEIGHT = 3;
protected static final int DIRECTORY_PATH_SORT_WEIGHT = 4;
protected static final int FILE_PATH_SORT_WEIGHT = 5;
protected static final int CHANGE_SORT_WEIGHT = 6;
protected static final int VIRTUAL_FILE_SORT_WEIGHT = 7;
protected static final int UNVERSIONED_SORT_WEIGHT = 8;
protected static final int DEFAULT_SORT_WEIGHT = 9;
protected static final int IGNORED_SORT_WEIGHT = 10;
public static final Convertor<TreePath, String> TO_TEXT_CONVERTER =
path -> ((ChangesBrowserNode)path.getLastPathComponent()).getTextPresentation();
private SimpleTextAttributes myAttributes;
private int myFileCount = -1;
private int myDirectoryCount = -1;
protected ChangesBrowserNode(Object userObject) {
super(userObject);
myAttributes = SimpleTextAttributes.REGULAR_ATTRIBUTES;
}
@NotNull
public static ChangesBrowserNode create(@NotNull LocallyDeletedChange change) {
return new ChangesBrowserLocallyDeletedNode(change);
}
@NotNull
public static ChangesBrowserNode create(@NotNull Project project, @NotNull Object userObject) {
if (userObject instanceof Change) {
return new ChangesBrowserChangeNode(project, (Change) userObject, null);
}
if (userObject instanceof VirtualFile) {
return new ChangesBrowserFileNode(project, (VirtualFile) userObject);
}
if (userObject instanceof FilePath) {
return new ChangesBrowserFilePathNode((FilePath) userObject);
}
if (userObject == LOCKED_FOLDERS_TAG) {
return new ChangesBrowserLockedFoldersNode(project, userObject);
}
if (userObject instanceof ChangesBrowserLogicallyLockedFile) {
return (ChangesBrowserNode) userObject;
}
return new ChangesBrowserNode(userObject);
}
@Override
public void insert(MutableTreeNode newChild, int childIndex) {
super.insert(newChild, childIndex);
resetFileCounters();
}
@Override
public void remove(int childIndex) {
super.remove(childIndex);
resetFileCounters();
}
protected boolean isFile() {
return false;
}
protected boolean isDirectory() {
return false;
}
public int getFileCount() {
if (myFileCount == -1) {
myFileCount = (isFile() ? 1 : 0) + toStream(children()).mapToInt(ChangesBrowserNode::getFileCount).sum();
}
return myFileCount;
}
public int getDirectoryCount() {
if (myDirectoryCount == -1) {
myDirectoryCount = (isDirectory() ? 1 : 0) + toStream(children()).mapToInt(ChangesBrowserNode::getDirectoryCount).sum();
}
return myDirectoryCount;
}
private void resetFileCounters() {
myFileCount = -1;
myDirectoryCount = -1;
}
@NotNull
public List<Change> getAllChangesUnder() {
return getAllObjectsUnder(Change.class);
}
@NotNull
public <U> List<U> getAllObjectsUnder(@NotNull Class<U> clazz) {
return getObjectsUnderStream(clazz).collect(Collectors.toList());
}
@NotNull
public <U> Stream<U> getObjectsUnderStream(@NotNull Class<U> clazz) {
return toStream(preorderEnumeration())
.map(ChangesBrowserNode::getUserObject)
.filter(userObject -> clazz.isAssignableFrom(userObject.getClass()))
.map(clazz::cast);
}
@NotNull
public List<VirtualFile> getAllFilesUnder() {
return getFilesUnderStream().collect(Collectors.toList());
}
@NotNull
public Stream<VirtualFile> getFilesUnderStream() {
return toStream(breadthFirstEnumeration())
.map(ChangesBrowserNode::getUserObject)
.filter(userObject -> userObject instanceof VirtualFile)
.map(VirtualFile.class::cast)
.filter(VirtualFile::isValid);
}
@NotNull
public List<FilePath> getAllFilePathsUnder() {
return getFilePathsUnderStream().collect(Collectors.toList());
}
@NotNull
public Stream<FilePath> getFilePathsUnderStream() {
return toStream(breadthFirstEnumeration())
.filter(ChangesBrowserNode::isLeaf)
.map(ChangesBrowserNode::getUserObject)
.filter(userObject -> userObject instanceof FilePath)
.map(FilePath.class::cast);
}
@NotNull
private static Stream<ChangesBrowserNode> toStream(@NotNull Enumeration enumeration) {
//noinspection unchecked
Iterator<ChangesBrowserNode> iterator = ContainerUtil.iterate((Enumeration<ChangesBrowserNode>)enumeration);
Spliterator<ChangesBrowserNode> spliterator = Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED | Spliterator.NONNULL);
return StreamSupport.stream(spliterator, false);
}
public void render(@NotNull ChangesBrowserNodeRenderer renderer, boolean selected, boolean expanded, boolean hasFocus) {
renderer.append(userObject.toString(), myAttributes);
appendCount(renderer);
}
@NotNull
protected String getCountText() {
int count = getFileCount();
int dirCount = getDirectoryCount();
String result = "";
if (dirCount != 0 || count != 0) {
result = spaceAndThinSpace() +
(dirCount == 0
? VcsBundle.message("changes.nodetitle.changecount", count)
: count == 0
? VcsBundle.message("changes.nodetitle.directory.changecount", dirCount)
: VcsBundle.message("changes.nodetitle.directory.file.changecount", dirCount, count));
}
return result;
}
protected void appendCount(@NotNull ColoredTreeCellRenderer renderer) {
renderer.append(getCountText(), SimpleTextAttributes.GRAYED_ATTRIBUTES);
}
public String toString() {
return getTextPresentation();
}
public String getTextPresentation() {
return userObject == null ? "" : userObject.toString();
}
@Override
public T getUserObject() {
//noinspection unchecked
return (T) userObject;
}
public boolean canAcceptDrop(final ChangeListDragBean dragBean) {
return false;
}
public void acceptDrop(final ChangeListOwner dragOwner, final ChangeListDragBean dragBean) {
}
/**
* Nodes with the same sort weight should share {@link #compareUserObjects} implementation
*/
public int getSortWeight() {
return DEFAULT_SORT_WEIGHT;
}
public int compareUserObjects(final Object o2) {
return 0;
}
public void setAttributes(@NotNull SimpleTextAttributes attributes) {
myAttributes = attributes;
}
protected void appendUpdatingState(@NotNull ChangesBrowserNodeRenderer renderer) {
renderer.append((getCountText().isEmpty() ? spaceAndThinSpace() : ", ") + VcsBundle.message("changes.nodetitle.updating"),
SimpleTextAttributes.GRAYED_ATTRIBUTES);
}
@Deprecated
public final int getCount() {
return getFileCount();
}
private static class Tag {
@NotNull private final String myKey;
public Tag(@NotNull String key) {
myKey = key;
}
@Override
public String toString() {
return VcsBundle.message(myKey);
}
}
}