/*******************************************************************************
* Copyright (C) 2011, 2014 Bernard Leach <leachbj@bouncycastle.org> and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.egit.ui.internal.staging;
import static org.eclipse.egit.ui.internal.staging.StagingEntry.State.ADDED;
import static org.eclipse.egit.ui.internal.staging.StagingEntry.State.CHANGED;
import static org.eclipse.egit.ui.internal.staging.StagingEntry.State.CONFLICTING;
import static org.eclipse.egit.ui.internal.staging.StagingEntry.State.MISSING;
import static org.eclipse.egit.ui.internal.staging.StagingEntry.State.MISSING_AND_CHANGED;
import static org.eclipse.egit.ui.internal.staging.StagingEntry.State.MODIFIED;
import static org.eclipse.egit.ui.internal.staging.StagingEntry.State.MODIFIED_AND_ADDED;
import static org.eclipse.egit.ui.internal.staging.StagingEntry.State.MODIFIED_AND_CHANGED;
import static org.eclipse.egit.ui.internal.staging.StagingEntry.State.REMOVED;
import static org.eclipse.egit.ui.internal.staging.StagingEntry.State.UNTRACKED;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.egit.core.internal.indexdiff.IndexDiffData;
import org.eclipse.egit.ui.internal.staging.StagingView.Presentation;
import org.eclipse.egit.ui.internal.staging.StagingView.StagingViewUpdate;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.ui.model.WorkbenchContentProvider;
/**
* ContentProvider for staged and unstaged tree nodes
*/
public class StagingViewContentProvider extends WorkbenchContentProvider {
/** All files for the section (staged or unstaged). */
private StagingEntry[] content = new StagingEntry[0];
/** Root nodes for the "Tree" presentation. */
private Object[] treeRoots;
/** Root nodes for the "Compact Tree" presentation. */
private Object[] compactTreeRoots;
private StagingView stagingView;
private boolean unstagedSection;
private Repository repository;
private final EntryComparator comparator;
StagingViewContentProvider(StagingView stagingView, boolean unstagedSection) {
this.stagingView = stagingView;
this.unstagedSection = unstagedSection;
comparator = new EntryComparator();
}
@Override
public Object getParent(Object element) {
if (element instanceof StagingFolderEntry)
return ((StagingFolderEntry) element).getParent();
if (element instanceof StagingEntry)
return ((StagingEntry) element).getParent();
return null;
}
@Override
public boolean hasChildren(Object element) {
return !(element instanceof StagingEntry);
}
@Override
public Object[] getElements(Object inputElement) {
return getChildren(inputElement);
}
@Override
public Object[] getChildren(Object parentElement) {
if (repository == null)
return new Object[0];
if (parentElement instanceof StagingEntry)
return new Object[0];
if (parentElement instanceof StagingFolderEntry) {
return ((StagingFolderEntry) parentElement).getChildren();
} else {
// Return the root nodes
if (stagingView.getPresentation() == Presentation.LIST)
return content;
else
return getTreePresentationRoots();
}
}
Object[] getTreePresentationRoots() {
Presentation presentation = stagingView.getPresentation();
switch (presentation) {
case COMPACT_TREE:
return getCompactTreeRoots();
case TREE:
return getTreeRoots();
default:
return new StagingFolderEntry[0];
}
}
private Object[] getCompactTreeRoots() {
if (compactTreeRoots == null)
compactTreeRoots = calculateTreePresentationRoots(true);
return compactTreeRoots;
}
private Object[] getTreeRoots() {
if (treeRoots == null)
treeRoots = calculateTreePresentationRoots(false);
return treeRoots;
}
private Object[] calculateTreePresentationRoots(boolean compact) {
if (content == null || content.length == 0)
return new Object[0];
List<Object> roots = new ArrayList<>();
Map<IPath, List<Object>> childrenForPath = new HashMap<>();
Set<IPath> folderPaths = new HashSet<>();
Map<IPath, String> childSegments = new HashMap<>();
for (StagingEntry file : content) {
IPath folderPath = file.getParentPath();
if (folderPath.segmentCount() == 0) {
// No folders need to be created, this is a root file
roots.add(file);
continue;
}
folderPaths.add(folderPath);
addChild(childrenForPath, folderPath, file);
for (IPath p = folderPath; p.segmentCount() != 1; p = p
.removeLastSegments(1)) {
IPath parent = p.removeLastSegments(1);
if (!compact) {
folderPaths.add(parent);
} else {
String childSegment = p.lastSegment();
String knownChildSegment = childSegments.get(parent);
if (knownChildSegment == null) {
childSegments.put(parent, childSegment);
} else if (!childSegment.equals(knownChildSegment)) {
// The parent has more than 1 direct child folder -> we
// need to make a node for it.
folderPaths.add(parent);
}
}
}
}
IPath workingDirectory = new Path(repository.getWorkTree()
.getAbsolutePath());
List<StagingFolderEntry> folderEntries = new ArrayList<>();
for (IPath folderPath : folderPaths) {
IPath parent = folderPath.removeLastSegments(1);
// Find first existing parent node, but stop at root
while (parent.segmentCount() != 0 && !folderPaths.contains(parent))
parent = parent.removeLastSegments(1);
if (parent.segmentCount() == 0) {
// Parent is root
StagingFolderEntry folderEntry = new StagingFolderEntry(
workingDirectory, folderPath, folderPath);
folderEntries.add(folderEntry);
roots.add(folderEntry);
} else {
// Parent is existing node
IPath nodePath = folderPath.makeRelativeTo(parent);
StagingFolderEntry folderEntry = new StagingFolderEntry(
workingDirectory, folderPath, nodePath);
folderEntries.add(folderEntry);
addChild(childrenForPath, parent, folderEntry);
}
}
for (StagingFolderEntry folderEntry : folderEntries) {
List<Object> children = childrenForPath.get(folderEntry.getPath());
if (children != null) {
for (Object child : children) {
if (child instanceof StagingEntry)
((StagingEntry) child).setParent(folderEntry);
else if (child instanceof StagingFolderEntry)
((StagingFolderEntry) child).setParent(folderEntry);
}
Collections.sort(children, comparator);
folderEntry.setChildren(children.toArray());
}
}
Collections.sort(roots, comparator);
return roots.toArray();
}
private static void addChild(Map<IPath, List<Object>> childrenForPath,
IPath path, Object child) {
List<Object> children = childrenForPath.get(path);
if (children == null) {
children = new ArrayList<>();
childrenForPath.put(path, children);
}
children.add(child);
}
int getShownCount() {
String filterString = getFilterString();
if (filterString.length() == 0) {
return getCount();
} else {
int shownCount = 0;
for (StagingEntry entry : content) {
if (isInFilter(entry))
shownCount++;
}
return shownCount;
}
}
List<StagingEntry> getStagingEntriesFiltered(StagingFolderEntry folder) {
List<StagingEntry> stagingEntries = new ArrayList<>();
for (StagingEntry stagingEntry : content) {
if (folder.getLocation().isPrefixOf(stagingEntry.getLocation())) {
if (isInFilter(stagingEntry))
stagingEntries.add(stagingEntry);
}
}
return stagingEntries;
}
boolean isInFilter(StagingEntry stagingEntry) {
String filterString = getFilterString();
return filterString.length() == 0
|| stagingEntry.getPath().toUpperCase(Locale.ROOT)
.contains(filterString.toUpperCase(Locale.ROOT));
}
private String getFilterString() {
return stagingView.getFilterString();
}
boolean hasVisibleChildren(StagingFolderEntry folder) {
if (getFilterString().length() == 0)
return true;
else
return !getStagingEntriesFiltered(folder).isEmpty();
}
StagingEntry[] getStagingEntries() {
return content;
}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
if (!(newInput instanceof StagingViewUpdate))
return;
StagingViewUpdate update = (StagingViewUpdate) newInput;
if (update.repository == null || update.indexDiff == null) {
content = new StagingEntry[0];
treeRoots = new Object[0];
compactTreeRoots = new Object[0];
return;
}
if (update.repository != repository) {
treeRoots = null;
compactTreeRoots = null;
}
repository = update.repository;
Set<StagingEntry> nodes = new TreeSet<>(
new Comparator<StagingEntry>() {
@Override
public int compare(StagingEntry o1, StagingEntry o2) {
return o1.getPath().compareTo(o2.getPath());
}
});
if (update.changedResources != null
&& !update.changedResources.isEmpty()) {
nodes.addAll(Arrays.asList(content));
for (String res : update.changedResources)
for (StagingEntry entry : content)
if (entry.getPath().equals(res))
nodes.remove(entry);
}
final IndexDiffData indexDiff = update.indexDiff;
if (unstagedSection) {
for (String file : indexDiff.getMissing())
if (indexDiff.getChanged().contains(file))
nodes.add(new StagingEntry(repository, MISSING_AND_CHANGED,
file));
else
nodes.add(new StagingEntry(repository, MISSING, file));
for (String file : indexDiff.getModified())
if (indexDiff.getChanged().contains(file))
nodes.add(new StagingEntry(repository, MODIFIED_AND_CHANGED,
file));
else if (indexDiff.getAdded().contains(file))
nodes.add(new StagingEntry(repository, MODIFIED_AND_ADDED,
file));
else
nodes.add(new StagingEntry(repository, MODIFIED, file));
for (String file : indexDiff.getUntracked())
nodes.add(new StagingEntry(repository, UNTRACKED, file));
for (String file : indexDiff.getConflicting())
nodes.add(new StagingEntry(repository, CONFLICTING, file));
} else {
for (String file : indexDiff.getAdded())
nodes.add(new StagingEntry(repository, ADDED, file));
for (String file : indexDiff.getChanged())
nodes.add(new StagingEntry(repository, CHANGED, file));
for (String file : indexDiff.getRemoved())
nodes.add(new StagingEntry(repository, REMOVED, file));
}
setSymlinkFileMode(indexDiff, nodes);
setSubmoduleFileMode(indexDiff, nodes);
content = nodes.toArray(new StagingEntry[nodes.size()]);
Arrays.sort(content, comparator);
treeRoots = null;
compactTreeRoots = null;
}
@Override
public void dispose() {
// nothing to dispose
}
/**
* @return StagingEntry count
*/
public int getCount() {
if (content == null)
return 0;
else
return content.length;
}
/**
* Set file name mode to be enabled or disabled, to keep proper sorting
* order. This mode displays the names of the file first followed by the
* path to the folder that the file is in.
*
* @param enable
*/
void setFileNameMode(boolean enable) {
comparator.fileNameMode = enable;
if (content != null) {
Arrays.sort(content, comparator);
}
}
private static class EntryComparator implements Comparator<Object> {
boolean fileNameMode;
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof StagingEntry) {
if (o2 instanceof StagingEntry) {
StagingEntry e1 = (StagingEntry) o1;
StagingEntry e2 = (StagingEntry) o2;
if (fileNameMode) {
int result = e1.getName().compareTo(e2.getName());
if (result != 0)
return result;
else
return e1.getParentPath().toString()
.compareTo(e2.getParentPath().toString());
}
return e1.getPath().compareTo(e2.getPath());
} else {
// Files should come after folders
return 1;
}
} else if (o1 instanceof StagingFolderEntry) {
if (o2 instanceof StagingFolderEntry) {
StagingFolderEntry f1 = (StagingFolderEntry) o1;
StagingFolderEntry f2 = (StagingFolderEntry) o2;
return f1.getPath().toString()
.compareTo(f2.getPath().toString());
} else {
// Folders should come before files
return -1;
}
} else {
return 0;
}
}
}
/**
* Set the symlink file mode of the given StagingEntries.
*
* @param indexDiff
* the index diff
* @param entries
* the given StagingEntries
*/
private void setSymlinkFileMode(IndexDiffData indexDiff,
Collection<StagingEntry> entries) {
final Set<String> symlinks = indexDiff.getSymlinks();
if (symlinks.isEmpty()) {
return;
}
for (StagingEntry stagingEntry : entries) {
if (symlinks.contains(stagingEntry.getPath()))
stagingEntry.setSymlink(true);
}
}
/**
* Set the submodule file mode (equivalent to FileMode.GITLINK) of the given
* StagingEntries.
*
* @param indexDiff
* the index diff
* @param entries
* the given StagingEntries
*/
private void setSubmoduleFileMode(IndexDiffData indexDiff,
Collection<StagingEntry> entries) {
final Set<String> submodules = indexDiff.getSubmodules();
if (submodules.isEmpty()) {
return;
}
for (StagingEntry stagingEntry : entries) {
if (submodules.contains(stagingEntry.getPath()))
stagingEntry.setSubmodule(true);
}
}
}