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

1. Arrangement API provides 'dependent entries' concept now;
2. Java static and instance initialization blocks are defined to be 'dependent arrangement entries';
3. Corresponding tests are added;
4. Default java arrangement rules are expanded in order to preserve old 'generated class members order';
5. Test data is corrected;
This commit is contained in:
Denis.Zhdanov
2012-09-06 13:21:41 +04:00
parent 4a4aa01505
commit e76b02dc4e
19 changed files with 300 additions and 31 deletions
@@ -45,7 +45,8 @@ public class JavaArrangementVisitor extends JavaElementVisitor {
MODIFIERS.put(PsiModifier.ABSTRACT, ArrangementModifier.ABSTRACT);
}
private final Stack<JavaElementArrangementEntry> myStack = new Stack<JavaElementArrangementEntry>();
@NotNull private final Stack<JavaElementArrangementEntry> myStack = new Stack<JavaElementArrangementEntry>();
@NotNull private final Map<PsiElement, JavaElementArrangementEntry> myEntries = new HashMap<PsiElement, JavaElementArrangementEntry>();
@NotNull private final List<JavaElementArrangementEntry> myRootEntries;
@NotNull private Collection<TextRange> myRanges;
@@ -69,13 +70,13 @@ public class JavaArrangementVisitor extends JavaElementVisitor {
else if (aClass.isInterface()) {
type = ArrangementEntryType.INTERFACE;
}
JavaElementArrangementEntry entry = createNewEntry(aClass.getTextRange(), type, aClass.getName(), true);
JavaElementArrangementEntry entry = createNewEntry(aClass, type, aClass.getName(), true);
processEntry(entry, aClass, aClass);
}
@Override
public void visitAnonymousClass(PsiAnonymousClass aClass) {
JavaElementArrangementEntry entry = createNewEntry(aClass.getTextRange(), ArrangementEntryType.CLASS, aClass.getName(), false);
JavaElementArrangementEntry entry = createNewEntry(aClass, ArrangementEntryType.CLASS, aClass.getName(), false);
processEntry(entry, null, aClass);
}
@@ -88,14 +89,43 @@ public class JavaArrangementVisitor extends JavaElementVisitor {
@Override
public void visitField(PsiField field) {
JavaElementArrangementEntry entry = createNewEntry(field.getTextRange(), ArrangementEntryType.FIELD, field.getName(), true);
JavaElementArrangementEntry entry = createNewEntry(field, ArrangementEntryType.FIELD, field.getName(), true);
processEntry(entry, field, field.getInitializer());
}
@Override
public void visitClassInitializer(PsiClassInitializer initializer) {
JavaElementArrangementEntry entry = createNewEntry(initializer, ArrangementEntryType.FIELD, null, true);
if (entry == null) {
return;
}
PsiElement classLBrace = null;
PsiClass clazz = initializer.getContainingClass();
if (clazz != null) {
classLBrace = clazz.getLBrace();
}
for (PsiElement e = initializer.getPrevSibling(); e != null; e = e.getPrevSibling()) {
JavaElementArrangementEntry prevEntry;
if (e == classLBrace) {
prevEntry = myEntries.get(clazz);
}
else {
prevEntry = myEntries.get(e);
}
if (prevEntry != null) {
entry.addDependency(prevEntry);
}
if (!(e instanceof PsiWhiteSpace)) {
break;
}
}
}
@Override
public void visitMethod(PsiMethod method) {
ArrangementEntryType type = method.isConstructor() ? ArrangementEntryType.CONSTRUCTOR : ArrangementEntryType.METHOD;
JavaElementArrangementEntry entry = createNewEntry(method.getTextRange(), type, method.getName(), true);
JavaElementArrangementEntry entry = createNewEntry(method, type, method.getName(), true);
processEntry(entry, method, method.getBody());
}
@@ -111,7 +141,7 @@ public class JavaArrangementVisitor extends JavaElementVisitor {
return;
}
JavaElementArrangementEntry entry =
createNewEntry(anonymousClass.getTextRange(), ArrangementEntryType.CLASS, anonymousClass.getName(), false);
createNewEntry(anonymousClass, ArrangementEntryType.CLASS, anonymousClass.getName(), false);
processEntry(entry, null, anonymousClass);
}
@@ -152,11 +182,12 @@ public class JavaArrangementVisitor extends JavaElementVisitor {
}
@Nullable
private JavaElementArrangementEntry createNewEntry(@NotNull TextRange range,
private JavaElementArrangementEntry createNewEntry(@NotNull PsiElement element,
@NotNull ArrangementEntryType type,
@Nullable String name,
boolean canArrange)
{
TextRange range = element.getTextRange();
if (!isWithinBounds(range)) {
return null;
}
@@ -170,13 +201,14 @@ public class JavaArrangementVisitor extends JavaElementVisitor {
else {
entry = new JavaElementArrangementEntry(current, range, type, name, false);
}
myEntries.put(element, entry);
if (current == null) {
myRootEntries.add(entry);
}
else {
current.addChild(entry);
}
return entry;
}
@@ -26,6 +26,8 @@ import java.util.EnumSet;
import java.util.Set;
/**
* Not thread-safe.
*
* @author Denis Zhdanov
* @since 7/20/12 4:50 PM
*/
@@ -88,9 +88,11 @@ public class JavaRearranger implements Rearranger<JavaElementArrangementEntry>,
and(FIELD, modifier);
}
and(CONSTRUCTOR);
and(METHOD, STATIC);
and(METHOD);
and(ENUM);
and(INTERFACE);
and(CLASS, STATIC);
and(CLASS);
}
@@ -183,8 +183,14 @@ public class JavaPsiImplementationHelperImpl extends JavaPsiImplementationHelper
CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(aClass.getProject());
MemberOrderService service = ServiceManager.getService(MemberOrderService.class);
PsiElement anchor = service.getAnchor(member, settings.getCommonSettings(JavaLanguage.INSTANCE), aClass);
if (anchor != null && anchor.getNextSibling() == aClass.getRBrace()) {
// Given member should be inserted as the last child.
return aClass.getRBrace();
}
if (anchor != null && anchor != aClass) {
anchor = anchor.getNextSibling();
while (anchor instanceof PsiJavaToken && (anchor.getText().equals(",") || anchor.getText().equals(";"))) {
anchor = anchor.getNextSibling();
}
@@ -194,7 +200,7 @@ public class JavaPsiImplementationHelperImpl extends JavaPsiImplementationHelper
}
// The main idea is to avoid to anchor to 'white space' element because that causes reformatting algorithm
// to perform incorrectly. The algorithm is encapsulated at PostprocessReformattingAspect.doPostponedFormattingInner().
// to perform incorrectly. The algorithm is encapsulated at the PostprocessReformattingAspect.doPostponedFormattingInner().
final PsiElement lBrace = aClass.getLBrace();
if (lBrace != null) {
PsiElement result = lBrace.getNextSibling();
@@ -207,7 +213,7 @@ public class JavaPsiImplementationHelperImpl extends JavaPsiImplementationHelper
return aClass.getRBrace();
}
// TODO remove as soon as arrangement sub-system is provided for groovy.
// TODO remove as soon as an arrangement sub-system is provided for groovy.
public static int getMemberOrderWeight(PsiElement member, CodeStyleSettings settings) {
if (member instanceof PsiField) {
if (member instanceof PsiEnumConstant) {
@@ -1,9 +1,25 @@
/*
* Copyright 2000-2012 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.
*/
// "Create Inner Class 'MyCollection'" "true"
public class Test {
public static void main() {
Collection[] cc = new MyCollection[10];
}
<caret>
private static class MyCollection {
}
}
@@ -1,3 +1,19 @@
/*
* Copyright 2000-2012 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.
*/
// "Create Inner Class 'MyTableModel'" "true"
import javax.swing.*;
import javax.swing.table.TableModel;
@@ -6,7 +22,7 @@ public class Test {
public static void main() {
JTable table = new JTable(new MyTableModel());
}
<caret>
private static class MyTableModel implements TableModel {
}
}
@@ -1,9 +1,25 @@
/*
* Copyright 2000-2012 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.
*/
// "Create Inner Class 'Generic'" "true"
class Test {
void foo () {
new Generic<String> ();
}
<caret>
private class Generic<T> {
}
}
@@ -58,7 +58,6 @@ public class TestClass {
return yearsTo( expiry );
}
return 0.0;
}
private double yearsTo( final Date endDate ) {
@@ -41,7 +41,6 @@ public class TestClass {
return yearsTo( expiry );
}
return 0.0;
}
private double riskFactor() {
@@ -1,7 +1,21 @@
/*
* Copyright 2000-2012 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.
*/
public class X {
A myField;
private final Base myDelegate = new Base();
A myField;
public void method(Test t) {
myField = t.getA();
myField.methodFromA();
@@ -1,6 +1,21 @@
/*
* Copyright 2000-2012 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.
*/
class A {
int fieldFromA;
private final MyBase myDelegate = new MyBase();
int fieldFromA;
public void firstMethodFromBase() {
myDelegate.firstMethodFromBase();
@@ -1,6 +1,21 @@
/*
* Copyright 2000-2012 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.
*/
class Test {
private Foo myFoo;
private final Foo foo;
private Foo myFoo;
Test() {
foo = new Foo();
@@ -1,6 +1,21 @@
/*
* Copyright 2000-2012 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.
*/
class Test {
private String myTimer;
private final String string;
private String myTimer;
Test() {
string = "";<caret>
@@ -1,6 +1,21 @@
/*
* Copyright 2000-2012 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.
*/
class Test {
private String myTimer;
private final String string;
private String myTimer;
Test() {
string = "";
@@ -69,4 +69,38 @@ class Test {
rule(CLASS)]
)
}
void testInstanceInitializationBlockBoundToField() {
doTest('''\
class Test {
private int i;
public int j;
{ j = 1; }
protected int k;
}''', '''\
class Test {
public int j;
{ j = 1; }
protected int k;
private int i;
}''',
[rule(FIELD, PUBLIC), rule(FIELD, PROTECTED), rule(FIELD, PRIVATE)])
}
void testInstanceInitializationBlockAsFirstChild() {
doTest('''\
class Test {
{ j = 1; }
private int i;
public int j;
protected int k;
}''', '''\
class Test {
{ j = 1; }
public int j;
protected int k;
private int i;
}''',
[rule(FIELD, PUBLIC), rule(FIELD, PROTECTED), rule(FIELD, PRIVATE)])
}
}
@@ -56,6 +56,20 @@ public interface ArrangementEntry {
@NotNull
List<? extends ArrangementEntry> getChildren();
/**
* There is a possible case that particular entry position depends on another entries positions. E.g. static initialization
* block which uses static fields from the same class must be declared after them.
* <p/>
* This method allows to answer what sibling entries must be located before the current one at the resulting arrangement algorithm.
* <p/>
* There is also a special case when a list with the single entry which is the current's entry {@link #getParent() parent}
* is returned - that means that current entry should be arranged to be the first child.
*
* @return current entry's dependencies (if any)
*/
@Nullable
List<? extends ArrangementEntry> getDependencies();
/**
* @return start offset of the current entry (inclusive) within the target document. Rearranger engine uses this information
* to move rearranged entries
@@ -22,13 +22,16 @@ import java.util.ArrayList;
import java.util.List;
/**
* Not thread-safe.
*
* @author Denis Zhdanov
* @since 7/20/12 4:53 PM
*/
public class DefaultArrangementEntry implements ArrangementEntry {
@NotNull private final List<ArrangementEntry> myChildren = new ArrayList<ArrangementEntry>();
private final List<ArrangementEntry> myChildren = new ArrayList<ArrangementEntry>();
@Nullable private List<ArrangementEntry> myDependencies;
@Nullable ArrangementEntry myParent;
private final int myStartOffset;
private final int myEndOffset;
@@ -58,6 +61,19 @@ public class DefaultArrangementEntry implements ArrangementEntry {
myChildren.add(entry);
}
@Nullable
@Override
public List<? extends ArrangementEntry> getDependencies() {
return myDependencies;
}
public void addDependency(@NotNull ArrangementEntry dependency) {
if (myDependencies == null) {
myDependencies = new ArrayList<ArrangementEntry>();
}
myDependencies.add(dependency);
}
@Override
public int getStartOffset() {
return myStartOffset;
@@ -112,7 +112,12 @@ public class MemberOrderService {
if (anchorEntry == null) {
return context;
}
return context.findElementAt(anchorEntry.getEndOffset() - context.getTextRange().getStartOffset());
int offset = anchorEntry.getEndOffset() - 1 - context.getTextRange().getStartOffset();
PsiElement element = context.findElementAt(offset);
for (PsiElement e = element; e != null && e.getTextRange().getStartOffset() >= anchorEntry.getStartOffset(); e = e.getParent()) {
element = e;
}
return element;
}
}
@@ -18,6 +18,7 @@ package com.intellij.psi.codeStyle.arrangement.engine;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDocumentManager;
@@ -30,6 +31,7 @@ import com.intellij.psi.codeStyle.arrangement.ArrangementRule;
import com.intellij.psi.codeStyle.arrangement.Rearranger;
import com.intellij.psi.codeStyle.arrangement.StdArrangementRule;
import com.intellij.psi.codeStyle.arrangement.settings.ArrangementStandardSettingsAware;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.Stack;
import com.intellij.util.text.CharArrayUtil;
import gnu.trove.TIntArrayList;
@@ -200,22 +202,58 @@ public class ArrangementEngine {
* @param <E> arrangement entry type
* @return arranged list of the given rules
*/
@SuppressWarnings("AssignmentToForLoopParameter")
@NotNull
public static <E extends ArrangementEntry> List<E> arrange(@NotNull Collection<E> entries,
@NotNull List<? extends ArrangementRule> rules)
{
List<E> arranged = new ArrayList<E>();
Set<E> unprocessed = new LinkedHashSet<E>(entries);
for (ArrangementRule rule : rules) {
for (E entry : entries) {
if (entry.canBeMatched() && unprocessed.contains(entry) && rule.getMatcher().isMatched(entry)) {
Set<E> unprocessed = new LinkedHashSet<E>();
List<Pair<Set<ArrangementEntry>, E>> dependent = new ArrayList<Pair<Set<ArrangementEntry>, E>>();
for (E entry : entries) {
List<? extends ArrangementEntry> dependencies = entry.getDependencies();
if (dependencies == null) {
unprocessed.add(entry);
}
else {
if (dependencies.size() == 1 && dependencies.get(0) == entry.getParent()) {
// Handle a situation when the entry is condifured to be at the first parent's children.
arranged.add(entry);
unprocessed.remove(entry);
}
else {
Set<ArrangementEntry> first = new HashSet<ArrangementEntry>(dependencies);
dependent.add(Pair.create(first, entry));
}
}
}
Set<E> matched = new HashSet<E>();
for (ArrangementRule rule : rules) {
matched.clear();
for (E entry : unprocessed) {
if (entry.canBeMatched() && rule.getMatcher().isMatched(entry)) {
arranged.add(entry);
matched.add(entry);
}
}
unprocessed.removeAll(matched);
}
arranged.addAll(unprocessed);
for (int i = 0; i < arranged.size() && !dependent.isEmpty(); i++) {
E e = arranged.get(i);
for (Iterator<Pair<Set<ArrangementEntry>, E>> iterator = dependent.iterator(); iterator.hasNext(); ) {
Pair<Set<ArrangementEntry>, E> pair = iterator.next();
pair.first.remove(e);
if (pair.first.isEmpty()) {
iterator.remove();
arranged.add(i + 1, pair.second);
i++;
}
}
}
return arranged;
}