IDEA-19061 Integrate the Rearranger-plugin into core-IDE

Added ability to remove match rules
This commit is contained in:
Denis.Zhdanov
2012-08-24 09:41:43 +04:00
parent 072879c5b7
commit 0e8672d58e
6 changed files with 225 additions and 110 deletions
@@ -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)
@@ -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()));
}
}
@@ -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<ArrangementTreeNode> toInsert = new ArrayList<ArrangementTreeNode>();
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);
}
}
@@ -35,6 +35,13 @@ public interface ArrangementRuleEditingModel {
@NotNull
ArrangementMatchCondition getMatchCondition();
/**
* Asks current model to destroy itself.
* <p/>
* 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.
@@ -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);
}
}
@@ -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<ArrangementRuleEditingModelImpl> changedModelMappings =
new TIntObjectHashMap<ArrangementRuleEditingModelImpl>();
final TIntObjectHashMap<ArrangementNodeComponent> changedRendererMappings = new TIntObjectHashMap<ArrangementNodeComponent>();
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<ArrangementRuleEditingModelImpl> changedModelMappings =
new TIntObjectHashMap<ArrangementRuleEditingModelImpl>();
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 <T> void putAll(@NotNull TIntObjectHashMap<T> from, @NotNull final TIntObjectHashMap<T> to) {
from.forEachEntry(new TIntObjectProcedure<T>() {
@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<ArrangementRuleEditingModelImpl> {