Tree structure for Dir Diff

This commit is contained in:
Konstantin Bulenkov
2011-04-18 18:05:48 +04:00
parent 9ef4ac79bb
commit 98195ed940
6 changed files with 322 additions and 120 deletions
@@ -26,4 +26,8 @@ public class DirDiffSettings {
public boolean showDifferent = true;
public boolean showNewOnSource = true;
public boolean showNewOnTarget = true;
public boolean compareByContent = true;
public String filter = "*.*";
}
@@ -0,0 +1,224 @@
/*
* Copyright 2000-2011 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.diff.impl.dir;
import com.intellij.ide.diff.DiffElement;
import com.intellij.ide.diff.DirDiffSettings;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.SortedList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
/**
* @author Konstantin Bulenkov
*/
public class DTree {
private static final Comparator<DTree> COMPARATOR = new Comparator<DTree>() {
@Override
public int compare(DTree o1, DTree o2) {
final boolean b1 = o1.isContainer();
final boolean b2 = o2.isContainer();
return (b1 && b2) || (!b1 && !b2)
? o1.getName().compareToIgnoreCase(o2.getName())
: b1 ? 1 : -1;
}
};
private boolean expanded = true;
@Nullable private final DTree myParent;
private HashMap<String, DTree> children;
private String myName;
private final boolean isContainer;
private SortedList<DTree> myChildrenList;
private DiffElement<?> mySource;
private DiffElement<?> myTarget;
private DType type;
private boolean myVisible = true;
public DTree(@Nullable DTree parent, @NotNull String name, boolean container) {
this.myParent = parent;
this.myName = name;
isContainer = container;
}
public Collection<DTree> getChildren() {
init();
if (myChildrenList == null) {
myChildrenList = new SortedList<DTree>(COMPARATOR);
myChildrenList.addAll(children.values());
}
return myChildrenList;
}
public DTree addChild(@NotNull DiffElement element, boolean source) {
init();
myChildrenList = null;
final DTree node;
final String name = element.getName();
if (children.containsKey(name)) {
node = children.get(name);
} else {
node = new DTree(this, name, element.isContainer());
children.put(name, node);
}
if (source) {
node.setSource(element);
} else {
node.setTarget(element);
}
return node;
}
public DiffElement<?> getSource() {
return mySource;
}
public void setSource(DiffElement<?> source) {
mySource = source;
}
public DiffElement<?> getTarget() {
return myTarget;
}
public void setTarget(DiffElement<?> target) {
myTarget = target;
}
private void init() {
if (children == null) {
children = new HashMap<String, DTree>();
}
}
public String getName() {
return myName;
}
@Nullable
public DTree getParent() {
return myParent;
}
public boolean isExpanded() {
return expanded;
}
public void setExpanded(boolean expanded) {
this.expanded = expanded;
}
public boolean isContainer() {
return isContainer;
}
@Override
public String toString() {
return myName;
}
public void update(DirDiffSettings settings) {
for (DTree tree : getChildren()) {
final DiffElement<?> src = tree.getSource();
final DiffElement<?> trg = tree.getTarget();
if (src == null && trg != null) {
tree.setType(DType.TARGET);
} else if (src != null && trg == null) {
tree.setType(DType.SOURCE);
} else {
assert src != null;
DType dtype = src.getSize() == trg.getSize() ? DType.EQUAL : DType.CHANGED;
if (dtype == DType.EQUAL && settings.compareByContent) {
dtype = isEqual(src, trg) ? DType.EQUAL : DType.CHANGED;
}
tree.setType(dtype);
}
tree.update(settings);
}
}
public boolean isVisible() {
return myVisible;
}
public void updateVisibility(DirDiffSettings settings) {
if (children.isEmpty()) {
switch (type) {
case SOURCE:
myVisible = settings.showNewOnSource;
break;
case TARGET:
myVisible = settings.showNewOnTarget;
break;
case SEPARATOR:
myVisible = true;
break;
case CHANGED:
myVisible = settings.showDifferent;
break;
case EQUAL:
myVisible = settings.showEqual;
break;
}
} else {
myVisible = false;
for (DTree child : children.values()) {
child.updateVisibility(settings);
myVisible = myVisible || child.isVisible();
}
}
}
private static boolean isEqual(DiffElement file1, DiffElement file2) {
if (file1.isContainer() || file2.isContainer()) return false;
if (file1.getSize() != file2.getSize()) return false;
try {
return Arrays.equals(file1.getContent(), file2.getContent());
}
catch (IOException e) {
return false;
}
}
public DType getType() {
return type;
}
public void setType(DType type) {
this.type = type;
}
public String getPath() {
final DTree parent = getParent();
if (parent != null) {
return parent.getPath() + getName() + (isContainer ? getSeparator() : "");
} else {
return getName() + (isContainer ? getSeparator() : "");
}
}
private String getSeparator() {
final String s = mySource != null ? mySource.getSeparator() : myTarget != null ? myTarget.getSeparator() : "";
return s;
}
}
@@ -0,0 +1,21 @@
/*
* Copyright 2000-2011 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.diff.impl.dir;
/**
* @author Konstantin Bulenkov
*/
public enum DType {SOURCE, TARGET, SEPARATOR, CHANGED, EQUAL}
@@ -22,7 +22,6 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.sql.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import static com.intellij.openapi.diff.impl.dir.DirDiffOperation.*;
@@ -30,7 +29,7 @@ import static com.intellij.openapi.diff.impl.dir.DirDiffOperation.*;
* @author Konstantin Bulenkov
*/
public class DirDiffElement {
private final ElementType myType;
private final DType myType;
private final DiffElement mySource;
private final long mySourceLength;
private final DiffElement myTarget;
@@ -38,7 +37,7 @@ public class DirDiffElement {
private final String myName;
private DirDiffOperation myOperation;
private DirDiffElement(@Nullable DiffElement source, @Nullable DiffElement target, ElementType type, String name) {
private DirDiffElement(@Nullable DiffElement source, @Nullable DiffElement target, DType type, String name) {
myType = type;
mySource = source;
mySourceLength = source == null || source.isContainer() ? -1 : source.getSize();
@@ -51,7 +50,7 @@ public class DirDiffElement {
else if (isTarget()) {
myOperation = DirDiffOperation.COPY_FROM;
}
else if (type == ElementType.CHANGED) {
else if (type == DType.CHANGED) {
assert source != null;
myOperation = source.getFileType().isBinary() ? NONE : DirDiffOperation.MERGE;
}
@@ -66,26 +65,30 @@ public class DirDiffElement {
}
private static String getLastModification(DiffElement file) {
return SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM).format(new Date(file.getModificationStamp()));
return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM).format(new Date(file.getModificationStamp()));
}
public static DirDiffElement createChange(@NotNull DiffElement source, @NotNull DiffElement target) {
return new DirDiffElement(source, target, ElementType.CHANGED, source.getName());
return new DirDiffElement(source, target, DType.CHANGED, source.getName());
}
public static DirDiffElement createSourceOnly(@NotNull DiffElement source) {
return new DirDiffElement(source, null, ElementType.SOURCE, null);
return new DirDiffElement(source, null, DType.SOURCE, null);
}
public static DirDiffElement createTargetOnly(@NotNull DiffElement target) {
return new DirDiffElement(null, target, ElementType.TARGET, null);
return new DirDiffElement(null, target, DType.TARGET, null);
}
public static DirDiffElement createDirElement(DiffElement src, DiffElement trg, String name) {
return new DirDiffElement(src, trg, ElementType.SEPARATOR, name);
return new DirDiffElement(src, trg, DType.SEPARATOR, name);
}
public ElementType getType() {
public static DirDiffElement createEqual(@NotNull DiffElement source, @NotNull DiffElement target) {
return new DirDiffElement(source, target, DType.EQUAL, source.getName());
}
public DType getType() {
return myType;
}
@@ -103,7 +106,7 @@ public class DirDiffElement {
@Nullable
public String getSourceName() {
return myType == ElementType.CHANGED || myType == ElementType.SOURCE
return myType == DType.CHANGED || myType == DType.SOURCE
? mySource.getName() : null;
}
@@ -114,7 +117,7 @@ public class DirDiffElement {
@Nullable
public String getTargetName() {
return myType == ElementType.CHANGED || myType == ElementType.TARGET
return myType == DType.CHANGED || myType == DType.TARGET
? myTarget.getName() : null;
}
@@ -124,21 +127,25 @@ public class DirDiffElement {
}
public boolean isSeparator() {
return myType == ElementType.SEPARATOR;
return myType == DType.SEPARATOR;
}
public boolean isSource() {
return myType == ElementType.SOURCE;
return myType == DType.SOURCE;
}
public boolean isTarget() {
return myType == ElementType.TARGET;
return myType == DType.TARGET;
}
public DirDiffOperation getOperation() {
return myOperation;
}
public boolean isContainer() {
return mySource == null ? myTarget.isContainer() : mySource.isContainer();
}
public void setNextOperation() {
final DirDiffOperation o = myOperation;
if (isSource()) {
@@ -150,8 +157,6 @@ public class DirDiffElement {
}
}
public static enum ElementType {SOURCE, TARGET, SEPARATOR, CHANGED}
public Icon getIcon() {
return mySource != null ? mySource.getIcon() : myTarget.getIcon();
}
@@ -86,7 +86,7 @@ public class DirDiffPanel {
final DirDiffElement element = myModel.getElementAt(myTable.getSelectedRow());
final Project project = myModel.getProject();
clearDiffPanel();
if (element.getType() == DirDiffElement.ElementType.CHANGED) {
if (element.getType() == DType.CHANGED) {
myDiffPanelComponent = element.getSource().getDiffComponent(element.getTarget(), project, myDialog.getWindow());
if (myDiffPanelComponent != null) {
myDiffPanel.add(myDiffPanelComponent, BorderLayout.CENTER);
@@ -19,12 +19,12 @@ import com.intellij.ide.diff.DiffElement;
import com.intellij.ide.diff.DirDiffSettings;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.Nullable;
import javax.swing.table.AbstractTableModel;
import java.io.IOException;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author Konstantin Bulenkov
@@ -36,109 +36,77 @@ public class DirDiffTableModel extends AbstractTableModel {
private final Project myProject;
private final DirDiffSettings mySettings;
private DiffElement mySrc;
private HashMap<String, DiffElement> mySrcPaths = new HashMap<String, DiffElement>();
private HashMap<String, DiffElement> myTrgPaths = new HashMap<String, DiffElement>();
private DiffElement myTrg;
final List<DirDiffElement> myElements = new ArrayList<DirDiffElement>();
public DirDiffTableModel(Project project, DiffElement src, DiffElement trg, ProgressIndicator indicator, DirDiffSettings settings) {
myProject = project;
mySettings = settings;
loadModel(src, trg, indicator);
}
public void loadModel(DiffElement src, DiffElement trg, ProgressIndicator indicator) {
mySrc = src;
myTrg = trg;
scan("", src, mySrcPaths, indicator, true);
scan("", trg, myTrgPaths, indicator, true);
reloadModel(indicator);
}
final HashSet<String> files = new HashSet<String>();
files.addAll(mySrcPaths.keySet());
files.addAll(myTrgPaths.keySet());
final ArrayList<String> pathes = new ArrayList<String>(files);
Collections.sort(pathes, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
final boolean b1 = o1.endsWith("/");
final boolean b2 = o2.endsWith("/");
final String[] dirs1 = o1.split("/");
final String[] dirs2 = o2.split("/");
final int len1 = dirs1.length;
final int len2 = dirs2.length;
public void reloadModel(ProgressIndicator indicator) {
final DTree tree = new DTree(null, "", true);
scan(mySrc, tree, true);
scan(myTrg, tree, false);
if ((!b1 && len1 == 1) || (!b2 && len2 == 1)) {
if ((!b1 && len1 == 1) && (!b2 && len2 == 1)) {
return dirs1[0].toLowerCase().compareTo(dirs2[0].toLowerCase());
} else {
return len1 == 1 ? -1 : 1;
tree.setSource(mySrc);
tree.setTarget(myTrg);
tree.update(mySettings);
tree.updateVisibility(mySettings);
myElements.clear();
fillElements(tree);
}
private void fillElements(DTree tree) {
boolean separatorAdded = tree.getParent() == null;
for (DTree child : tree.getChildren()) {
if (!child.isContainer()) {
if (child.isVisible()) {
if (!separatorAdded) {
myElements.add(DirDiffElement.createDirElement(tree.getSource(), tree.getTarget(), tree.getPath()));
separatorAdded = true;
}
switch (child.getType()) {
case SOURCE:
myElements.add(DirDiffElement.createSourceOnly(child.getSource()));
break;
case TARGET:
myElements.add(DirDiffElement.createTargetOnly(child.getTarget()));
break;
case CHANGED:
myElements.add(DirDiffElement.createChange(child.getSource(), child.getTarget()));
break;
case EQUAL:
myElements.add(DirDiffElement.createEqual(child.getSource(), child.getTarget()));
break;
}
}
for (int i = 0; i < Math.min(len1, len2); i++) {
final int cmp = dirs1[i].toLowerCase().compareTo(dirs2[i].toLowerCase());
if (cmp != 0) return cmp;
}
return len1 - len2;
} else {
fillElements(child);
}
});
}
}
for (String path : pathes) {
final DiffElement srcFile = mySrcPaths.get(path);
final DiffElement trgFile = myTrgPaths.get(path);
if (srcFile == null && trgFile != null) {
myElements.add(trgFile.isContainer() ? DirDiffElement.createDirElement(srcFile, trgFile, path) : DirDiffElement.createTargetOnly(trgFile));
} else if (srcFile != null && trgFile == null) {
myElements.add(srcFile.isContainer() ? DirDiffElement.createDirElement(srcFile, trgFile, path) : DirDiffElement.createSourceOnly(srcFile));
} else if (srcFile != null && trgFile != null) {
indicator.setText2("Comparing " + path);
if (srcFile.isContainer() && trgFile.isContainer()) {
myElements.add(DirDiffElement.createDirElement(srcFile, trgFile, path));
} else if (srcFile.isContainer() && !trgFile.isContainer()) {
myElements.add(DirDiffElement.createDirElement(srcFile, null, path));
myElements.add(DirDiffElement.createTargetOnly(trgFile));
} else if (!srcFile.isContainer() && trgFile.isContainer()) {
myElements.add(DirDiffElement.createDirElement(null, trgFile, path));
myElements.add(DirDiffElement.createSourceOnly(srcFile));
} else if (!isEqual(srcFile, trgFile)) {
myElements.add(DirDiffElement.createChange(srcFile, trgFile));
private static void scan(DiffElement element, DTree root, boolean source) {
if (element.isContainer()) {
try {
for (DiffElement child : element.getChildren()) {
scan(child, root.addChild(child, source), source);
}
}
catch (IOException e) {//
}
}
removeEmptyDirs(myElements);
}
public String getTitle() {
return "Diff for " + mySrc.getPath() + " and " + myTrg.getPath();
}
private static void removeEmptyDirs(List<DirDiffElement> elements) {
final DirDiffElement[] tmp = elements.toArray(new DirDiffElement[elements.size()]);
boolean prevItemIsSeparator = true;
for (int i = tmp.length - 1; i >= 0; i--) {
final boolean isSeparator = tmp[i].isSeparator();
if (isSeparator) {
if (prevItemIsSeparator) {
elements.remove(i);
}
prevItemIsSeparator = true;
} else {
prevItemIsSeparator = false;
}
}
}
private static boolean isEqual(DiffElement file1, DiffElement file2) {
if (file1.isContainer() || file2.isContainer()) return false;
if (file1.getSize() != file2.getSize()) return false;
try {
return Arrays.equals(file1.getContent(), file2.getContent());
}
catch (IOException e) {
return false;
}
}
public DirDiffElement getElementAt(int index) {
return myElements.get(index);
}
@@ -151,26 +119,6 @@ public class DirDiffTableModel extends AbstractTableModel {
return myTrg;
}
private static void scan(String prefix, DiffElement file, HashMap<String, DiffElement> files, ProgressIndicator indicator, boolean isRoot) {
if (file.isContainer()) {
indicator.setText2(file.getPath());
String p = isRoot ? "" : prefix + file.getName() + "/";
if (!isRoot) {
files.put(p, file);
}
try {
for (DiffElement f : file.getChildren()) {
scan(p, f, files, indicator, false);
}
}
catch (IOException e) {
//TODO: error message
}
} else {
files.put(prefix + file.getName(), file);
}
}
@Override
public int getRowCount() {
return myElements.size();