From 98195ed940133ec784900fc729c1a0f38fc2bb97 Mon Sep 17 00:00:00 2001 From: Konstantin Bulenkov Date: Mon, 18 Apr 2011 18:05:05 +0400 Subject: [PATCH] Tree structure for Dir Diff --- .../intellij/ide/diff/DirDiffSettings.java | 4 + .../intellij/openapi/diff/impl/dir/DTree.java | 224 ++++++++++++++++++ .../intellij/openapi/diff/impl/dir/DType.java | 21 ++ .../openapi/diff/impl/dir/DirDiffElement.java | 39 +-- .../openapi/diff/impl/dir/DirDiffPanel.java | 2 +- .../diff/impl/dir/DirDiffTableModel.java | 152 ++++-------- 6 files changed, 322 insertions(+), 120 deletions(-) create mode 100644 platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DTree.java create mode 100644 platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DType.java diff --git a/platform/platform-api/src/com/intellij/ide/diff/DirDiffSettings.java b/platform/platform-api/src/com/intellij/ide/diff/DirDiffSettings.java index 53c74b076c95..421ddff77750 100644 --- a/platform/platform-api/src/com/intellij/ide/diff/DirDiffSettings.java +++ b/platform/platform-api/src/com/intellij/ide/diff/DirDiffSettings.java @@ -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 = "*.*"; } diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DTree.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DTree.java new file mode 100644 index 000000000000..a9514f8d3e0e --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DTree.java @@ -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 COMPARATOR = new Comparator() { + @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 children; + private String myName; + private final boolean isContainer; + private SortedList 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 getChildren() { + init(); + if (myChildrenList == null) { + myChildrenList = new SortedList(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(); + } + } + + 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; + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DType.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DType.java new file mode 100644 index 000000000000..5d94f557b4d0 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DType.java @@ -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} diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffElement.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffElement.java index 7bd7e8545496..3a43ea6baa62 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffElement.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffElement.java @@ -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(); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffPanel.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffPanel.java index c0598d1a1de2..853a232aa2a2 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffPanel.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffPanel.java @@ -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); diff --git a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffTableModel.java b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffTableModel.java index bd85826e5af5..6ab25e24c2ab 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffTableModel.java +++ b/platform/vcs-impl/src/com/intellij/openapi/diff/impl/dir/DirDiffTableModel.java @@ -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 mySrcPaths = new HashMap(); - private HashMap myTrgPaths = new HashMap(); private DiffElement myTrg; final List myElements = new ArrayList(); 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 files = new HashSet(); - files.addAll(mySrcPaths.keySet()); - files.addAll(myTrgPaths.keySet()); - final ArrayList pathes = new ArrayList(files); - Collections.sort(pathes, new Comparator() { - @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 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 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();