From 0e8672d58efebc94f44bdb6c8b55917ca58d5545 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Fri, 24 Aug 2012 09:41:43 +0400 Subject: [PATCH] IDEA-19061 Integrate the Rearranger-plugin into core-IDE Added ability to remove match rules --- .../ArrangementConfigUtilTest.groovy | 68 ++++++++-- .../ArrangementRuleEditingModelImplTest.java | 12 +- .../arrangement/ArrangementConfigUtil.java | 50 ++++++-- .../ArrangementRuleEditingModel.java | 7 ++ .../ArrangementRuleEditingModelImpl.java | 80 ++++++------ .../arrangement/ArrangementRuleTree.java | 118 ++++++++++-------- 6 files changed, 225 insertions(+), 110 deletions(-) diff --git a/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementConfigUtilTest.groovy b/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementConfigUtilTest.groovy index ba96018c4730..50285ed58776 100644 --- a/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementConfigUtilTest.groovy +++ b/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementConfigUtilTest.groovy @@ -51,7 +51,7 @@ four = '4'() '1' { '4'() } - def rowMappings = doReplace(initial, one, four, replacement) + def rowMappings = doReplace(one, four, replacement) // Check def expected = new TreeNodeBuilder(). @@ -88,7 +88,7 @@ to = '5'() '1' { '5'() } - def rowMappings = doReplace(initial, from, to, replacement) + def rowMappings = doReplace(from, to, replacement) // Check. def expected = new TreeNodeBuilder(). @@ -123,7 +123,7 @@ node = '4'() '1' { '4'() } - def rowMappings = doReplace(initial, node, node, replacement) + def rowMappings = doReplace(node, node, replacement) // Check. def expected = new TreeNodeBuilder(). @@ -158,7 +158,7 @@ to = '3'()} '4' { '3'() } - def rowMappings = doReplace(initial, from, to, replacement) + def rowMappings = doReplace(from, to, replacement) // Check. def expected = new TreeNodeBuilder(). @@ -194,7 +194,7 @@ to = '2'()} '3' { '2'() } - def rowMappings = doReplace(initial, from, to, replacement) + def rowMappings = doReplace(from, to, replacement) // Check. def expected = new TreeNodeBuilder(). @@ -361,15 +361,69 @@ to = '2'()} doInsert(initial, 3, toAdd) assertNodesEqual(expected, initial) } + + @Test + void removeFirst() { + def from; + def to; + def initial = new TreeNodeBuilder(). + '0' { +from = '1' { +to = '2'() + '3'()} + '4'() + } + + def expected = new TreeNodeBuilder(). + '0' { + '1' { + '3'()} + '4'() + } + + def rowMappings = doRemove(from, to) + assertNodesEqual(expected, initial) + checkRowMappings([ 3 : 2, 4 : 3 ], rowMappings) + } + + @Test + void removeWithMerge() { + def from; + def to; + def initial = new TreeNodeBuilder(). + '0' { + '1' { + '2'()} +from = '3'() { +to = '4'()} + '1' { + '5'()} + } + + def expected = new TreeNodeBuilder(). + '0' { + '1' { + '2'() + '5'()} + } + + def rowMappings = doRemove(from, to) + assertNodesEqual(expected, initial) + checkRowMappings([ 6 : 3 ], rowMappings) + } - private static def doReplace(initial, from, to, replacement) { - ArrangementConfigUtil.replace(from, to, replacement, new DefaultTreeModel(initial), true) + private static def doReplace(from, to, replacement) { + ArrangementConfigUtil.replace(from, to, replacement, new DefaultTreeModel(ArrangementConfigUtil.getRoot(from)), true) } private static def doInsert(parent, i, child) { ArrangementConfigUtil.insert(parent, i, child, new DefaultTreeModel(ArrangementConfigUtil.getRoot(parent))) } + private static def doRemove(from, to) { + ArrangementConfigUtil.remove(from, to, new DefaultTreeModel(ArrangementConfigUtil.getRoot(from)), true) + } + private static void assertNodesEqual(@NotNull ArrangementTreeNode expected, @NotNull ArrangementTreeNode actual) { assertEquals(expected.userObject, actual.userObject) assertEquals(expected.childCount, actual.childCount) diff --git a/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelImplTest.java b/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelImplTest.java index 6ad43d9a2071..458d70b42f27 100644 --- a/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelImplTest.java +++ b/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelImplTest.java @@ -16,6 +16,7 @@ package com.intellij.application.options.codeStyle.arrangement; import com.intellij.psi.codeStyle.arrangement.model.ArrangementMatchCondition; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import static com.intellij.psi.codeStyle.arrangement.ArrangementUtil.and; @@ -90,7 +91,7 @@ public class ArrangementRuleEditingModelImplTest extends AbstractArrangementRule @Test public void removeAndKeepAllLevels() { configure(and(atom(FIELD), atom(PUBLIC), atom(STATIC))); - ArrangementRuleEditingModel model = myRowMappings.get(2); + ArrangementRuleEditingModelImpl model = myRowMappings.get(2); assertNotNull(model); assertEquals(1, myRowMappings.size()); @@ -107,6 +108,8 @@ public class ArrangementRuleEditingModelImplTest extends AbstractArrangementRule ArrangementTreeNode modifiersNode = fieldNode.getFirstChild(); assertNotNull(modifiersNode); assertEquals(atom(STATIC), modifiersNode.getBackingCondition()); + + checkModelTreeNodesConsistency(model); } @Test @@ -194,4 +197,11 @@ public class ArrangementRuleEditingModelImplTest extends AbstractArrangementRule assertNotNull(atomFieldNode); assertEquals(atom(FIELD), atomFieldNode.getBackingCondition()); } + + private void checkModelTreeNodesConsistency(@NotNull ArrangementRuleEditingModelImpl model) { + model.refreshTreeNodes(); + ArrangementTreeNode root = ArrangementConfigUtil.getRoot(model.getBottomMost()); + assertSame(myRoot, root); + assertSame(root, ArrangementConfigUtil.getRoot(model.getTopMost())); + } } diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementConfigUtil.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementConfigUtil.java index 9e87d263148d..f3d11ff7af16 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementConfigUtil.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementConfigUtil.java @@ -217,7 +217,7 @@ public class ArrangementConfigUtil { /** * Utility method which helps to replace node sub-hierarchy identified by the given start and end nodes (inclusive) by * a sub-hierarchy which is denoted by the given root. - * + * * @param from indicates start of the node sub-hierarchy (top-most node) to be replaced (inclusive) * @param to indicates end of the node sub-hierarchy (bottom-most node) to be replaced (inclusive) * @param replacement root of the node sub-hierarchy which should replace the one identified by the given 'start' and 'end' nodes @@ -225,20 +225,34 @@ public class ArrangementConfigUtil { * @param rootVisible determines if the root should be count during rows calculations * @return collection of row changes at the form {@code 'old row -> new row'} */ - @SuppressWarnings("AssignmentToForLoopParameter") - @NotNull public static TIntIntHashMap replace(@NotNull ArrangementTreeNode from, @NotNull ArrangementTreeNode to, @NotNull ArrangementTreeNode replacement, @NotNull DefaultTreeModel treeModel, boolean rootVisible) + { + return doReplace(from, to, replacement, treeModel, rootVisible); + } + + + + @SuppressWarnings("AssignmentToForLoopParameter") + @NotNull + private static TIntIntHashMap doReplace(@NotNull ArrangementTreeNode from, + @NotNull ArrangementTreeNode to, + @Nullable ArrangementTreeNode replacement, + @NotNull DefaultTreeModel treeModel, + boolean rootVisible) { markRows(from, rootVisible); if (from == to) { ArrangementTreeNode parent = from.getParent(); + assert parent != null; int index = parent.getIndex(from); treeModel.removeNodeFromParent(from); - insert(parent, index, replacement, treeModel); + if (replacement != null) { + insert(parent, index, replacement, treeModel); + } return collectRowChangesAndUnmark(parent, rootVisible); } @@ -298,11 +312,13 @@ public class ArrangementConfigUtil { // Note: we need to have a notion of 'equal nodes' for node re-usage. It's provided by comparing node user objects. final ArrangementTreeNode root = from.getParent(); + assert root != null; //region Cut bottom sub-hierarchy ArrangementTreeNode cutHierarchy = null; for (ArrangementTreeNode current = to; current != root; current = current.getParent()) { ArrangementTreeNode parent = current.getParent(); + assert parent != null; int i = parent.getIndex(current); int childCount = parent.getChildCount(); if (i >= childCount - 1) { @@ -331,7 +347,7 @@ public class ArrangementConfigUtil { ArrangementTreeNode parent = current.getParent(); treeModel.removeNodeFromParent(current); current = parent; - if (parent.getChildCount() > 0) { + if (parent == null || parent.getChildCount() > 0) { break; } } @@ -339,7 +355,9 @@ public class ArrangementConfigUtil { //region Insert nodes. int insertionIndex = root.getChildCount() < childCountBefore ? childCountBefore - 1 : childCountBefore; - insert(root, insertionIndex, replacement, treeModel); + if (replacement != null) { + insert(root, insertionIndex, replacement, treeModel); + } if (cutHierarchy != null) { List toInsert = new ArrayList(); if (hasEqualSetting(root, cutHierarchy)) { @@ -380,7 +398,7 @@ public class ArrangementConfigUtil { } @NotNull - public static ArrangementTreeNode getRoot(ArrangementTreeNode node) { + public static ArrangementTreeNode getRoot(@NotNull ArrangementTreeNode node) { ArrangementTreeNode root = node; for (ArrangementTreeNode n = root; n != null; n = n.getParent()) { root = n; @@ -536,4 +554,22 @@ public class ArrangementConfigUtil { return matchCondition1.equals(matchCondition2); } } + + /** + * Removes target sub-hierarchy from the tree. + * + * @param from indicates start of the node sub-hierarchy (top-most node) to be replaced (inclusive) + * @param to indicates end of the node sub-hierarchy (bottom-most node) to be replaced (inclusive) + * @param treeModel model which should hold ui nodes + * @param rootVisible determines if the root should be count during rows calculations + * @return collection of row changes at the form {@code 'old row -> new row'} + */ + @NotNull + public static TIntIntHashMap remove(@NotNull final ArrangementTreeNode from, + @NotNull final ArrangementTreeNode to, + @NotNull DefaultTreeModel model, + boolean rootVisible) + { + return doReplace(from, to, null, model, rootVisible); + } } diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModel.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModel.java index 363c6a4f3e3e..10b52602b114 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModel.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModel.java @@ -35,6 +35,13 @@ public interface ArrangementRuleEditingModel { @NotNull ArrangementMatchCondition getMatchCondition(); + /** + * Asks current model to destroy itself. + *

+ * The key concern here is to perform necessary tree modification. + */ + void destroy(); + /** * Allows to answer if current model has a registered condition for the given key. A key is expected to be one of the standard * keys, e.g. {@link ArrangementEntryType type}, {@link ArrangementModifier modifier} etc. diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelImpl.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelImpl.java index 4fb6f360c76a..2eb0eb1a2826 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelImpl.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelImpl.java @@ -144,11 +144,12 @@ public class ArrangementRuleEditingModelImpl implements ArrangementRuleEditingMo @Override public void removeAndCondition(@NotNull ArrangementMatchCondition condition) { - if (!(myMatchCondition instanceof ArrangementCompositeMatchCondition)) { - // TODO den implement + if (myMatchCondition.equals(condition)) { + destroy(); return; } - + + assert myMatchCondition instanceof ArrangementCompositeMatchCondition; ArrangementMatchCondition newCondition = myMatchCondition.clone(); ArrangementCompositeMatchCondition composite = (ArrangementCompositeMatchCondition)newCondition; composite.getOperands().remove(condition); @@ -166,49 +167,46 @@ public class ArrangementRuleEditingModelImpl implements ArrangementRuleEditingMo ArrangementTreeNode newTop = ArrangementConfigUtil.getRoot(newBottom); final TIntIntHashMap rowChanges = ArrangementConfigUtil.replace(myTopMost, myBottomMost, newTop, myTreeModel, myRootVisible); myBottomMost = newBottom; - for (ArrangementTreeNode node = myBottomMost.getParent(); node != null; node = node.getParent()) { - // There is a possible case that top condition is merged into existing one, hence, we need to refresh it. - ArrangementMatchCondition condition = node.getBackingCondition(); - if (condition != null && condition.equals(newTop.getBackingCondition())) { - newTop = node; - } - } myTopMost = newTop; - rowChanges.remove(myRow); + refreshTreeNodes(); int newRow = ArrangementConfigUtil.getRow(myBottomMost, myRootVisible); - rowChanges.put(myRow, newRow); - myRow = newRow; + if (myRow != newRow) { + rowChanges.put(myRow, newRow); + myRow = newRow; + } refreshConditions(); - notifyListeners(rowChanges); + for (Listener listener : myListeners) { + listener.onChanged(this, rowChanges); + } } @Override public void replaceCondition(@NotNull ArrangementAtomMatchCondition from, @NotNull ArrangementAtomMatchCondition to) throws IllegalArgumentException { - for (ArrangementTreeNode node = myBottomMost; node != null; node = node.getParent()) { - if (from.equals(node.getBackingCondition())) { - ArrangementMatchCondition newCondition; - if (myMatchCondition.equals(from)) { - newCondition = to; - } - else { - assert myMatchCondition instanceof ArrangementCompositeMatchCondition; - ArrangementCompositeMatchCondition composite = (ArrangementCompositeMatchCondition)myMatchCondition; - ArrangementCompositeMatchCondition newComposite = composite.clone(); - newComposite.getOperands().remove(from); - newComposite.getOperands().add(to); - newCondition = newComposite; - } - applyNewCondition(newCondition); - return; - } - if (node == myTopMost) { - throw new IllegalArgumentException(String.format( - "Can't perform arrangement match condition modification ('%s' -> '%s'). Reason: target condition doesn't have " - + "'%s' condition - %s", - from, to, from, myMatchCondition)); - } + ArrangementMatchCondition newCondition; + if (myMatchCondition.equals(from)) { + newCondition = to; + } + else { + assert myMatchCondition instanceof ArrangementCompositeMatchCondition; + ArrangementCompositeMatchCondition composite = (ArrangementCompositeMatchCondition)myMatchCondition; + ArrangementCompositeMatchCondition newComposite = composite.clone(); + newComposite.getOperands().remove(from); + newComposite.getOperands().add(to); + newCondition = newComposite; + } + applyNewCondition(newCondition); + } + + @Override + public void destroy() { + for (Listener listener : myListeners) { + listener.beforeModelDestroy(this); + } + TIntIntHashMap rowChanges = ArrangementConfigUtil.remove(myTopMost, myBottomMost, myTreeModel, myRootVisible); + for (Listener listener : myListeners) { + listener.afterModelDestroy(rowChanges); } } @@ -216,12 +214,6 @@ public class ArrangementRuleEditingModelImpl implements ArrangementRuleEditingMo myListeners.add(listener); } - private void notifyListeners(@NotNull TIntIntHashMap rowChanges) { - for (Listener listener : myListeners) { - listener.onChanged(this, rowChanges); - } - } - @Override public String toString() { return "model for " + myMatchCondition; @@ -246,5 +238,7 @@ public class ArrangementRuleEditingModelImpl implements ArrangementRuleEditingMo public interface Listener { void onChanged(@NotNull ArrangementRuleEditingModelImpl model, @NotNull TIntIntHashMap rowChanges); + void beforeModelDestroy(@NotNull ArrangementRuleEditingModelImpl model); + void afterModelDestroy(@NotNull TIntIntHashMap rowChanges); } } diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleTree.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleTree.java index e6a547658d1d..41523a9d4eae 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleTree.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleTree.java @@ -341,10 +341,10 @@ public class ArrangementRuleTree { } @NotNull - private ArrangementNodeComponent getNodeComponentAt(int row, @NotNull ArrangementMatchCondition node) { + private ArrangementNodeComponent getNodeComponentAt(int row, @NotNull ArrangementMatchCondition condition) { ArrangementNodeComponent result = myRenderers.get(row); - if (result == null) { - myRenderers.put(row, result = myFactory.getComponent(node)); + if (result == null || !result.getMatchCondition().equals(condition)) { + myRenderers.put(row, result = myFactory.getComponent(condition)); } return result; } @@ -434,55 +434,7 @@ public class ArrangementRuleTree { } private void onModelChange(@NotNull ArrangementRuleEditingModelImpl model, @NotNull final TIntIntHashMap rowChanges) { - expandAll(myTree, new TreePath(myTreeModel.getRoot())); - - // Refresh models. - myModels.forEachValue(myModelNodesRefresher); - - // Shift row-based caches. - final TIntObjectHashMap changedModelMappings = - new TIntObjectHashMap(); - final TIntObjectHashMap changedRendererMappings = new TIntObjectHashMap(); - rowChanges.forEachEntry(new TIntIntProcedure() { - @Override - public boolean execute(int oldRow, int newRow) { - ArrangementRuleEditingModelImpl m = myModels.remove(oldRow); - if (m != null) { - changedModelMappings.put(newRow, m); - } - - ArrangementNodeComponent renderer = myRenderers.remove(oldRow); - if (renderer != null) { - changedRendererMappings.put(newRow, renderer); - } - return true; - } - - - }); - putAll(changedModelMappings, myModels); - putAll(changedRendererMappings, myRenderers); - - // Drop JTree visual caches. - rowChanges.forEachEntry(new TIntIntProcedure() { - @Override - public boolean execute(int oldRow, int newRow) { - refreshTreeNode(oldRow); - refreshTreeNode(newRow); - return true; - } - private void refreshTreeNode(int row) { - TreePath path = myTree.getPathForRow(row); - if (path == null) { - return; - } - TreeNode node = (TreeNode)path.getLastPathComponent(); - if (node == null) { - return; - } - myTreeModel.nodeStructureChanged(node); - } - }); + processRowChanges(rowChanges); // Perform necessary actions for the changed model. ArrangementTreeNode topMost = model.getTopMost(); @@ -511,6 +463,51 @@ public class ArrangementRuleTree { } } + private void processRowChanges(TIntIntHashMap rowChanges) { + expandAll(myTree, new TreePath(myTreeModel.getRoot())); + + // Refresh models. + myModels.forEachValue(myModelNodesRefresher); + + // Shift row-based caches. + final TIntObjectHashMap changedModelMappings = + new TIntObjectHashMap(); + rowChanges.forEachEntry(new TIntIntProcedure() { + @Override + public boolean execute(int oldRow, int newRow) { + ArrangementRuleEditingModelImpl m = myModels.remove(oldRow); + if (m != null) { + changedModelMappings.put(newRow, m); + } + return true; + } + + + }); + putAll(changedModelMappings, myModels); + + // Drop JTree visual caches. + rowChanges.forEachEntry(new TIntIntProcedure() { + @Override + public boolean execute(int oldRow, int newRow) { + refreshTreeNode(oldRow); + refreshTreeNode(newRow); + return true; + } + private void refreshTreeNode(int row) { + TreePath path = myTree.getPathForRow(row); + if (path == null) { + return; + } + TreeNode node = (TreeNode)path.getLastPathComponent(); + if (node == null) { + return; + } + myTreeModel.nodeStructureChanged(node); + } + }); + } + private static void putAll(@NotNull TIntObjectHashMap from, @NotNull final TIntObjectHashMap to) { from.forEachEntry(new TIntObjectProcedure() { @Override @@ -590,6 +587,23 @@ public class ArrangementRuleTree { public void onChanged(@NotNull ArrangementRuleEditingModelImpl model, @NotNull TIntIntHashMap rowChanges) { onModelChange(model, rowChanges); } + + @Override + public void beforeModelDestroy(@NotNull ArrangementRuleEditingModelImpl model) { + for (ArrangementTreeNode node = model.getBottomMost(); node != null; node = node.getParent()) { + int row = myTree.getRowForPath(new TreePath(node.getPath())); + myRenderers.remove(row); + myModels.remove(row); + if (node == model.getTopMost()) { + break; + } + } + } + + @Override + public void afterModelDestroy(@NotNull TIntIntHashMap rowChanges) { + processRowChanges(rowChanges); + } } private static class MyModelNodesRefresher implements TObjectProcedure {