IDEA-124590 Java Rearranger: Implement "section" support

This commit is contained in:
Yaroslav Lepenkin
2014-05-22 11:55:26 +04:00
parent 61b6ca0352
commit f0fdc59195
4 changed files with 1182 additions and 37 deletions
@@ -18,11 +18,13 @@ package com.intellij.psi.codeStyle.arrangement;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.arrangement.group.ArrangementGroupingRule;
import com.intellij.psi.codeStyle.arrangement.std.ArrangementSettingsToken;
import com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens;
import com.intellij.psi.search.searches.SuperMethodsSearch;
import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.ContainerUtilRt;
@@ -32,6 +34,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.intellij.psi.codeStyle.arrangement.ArrangementSectionDetector.ArrangementSectionEntryTemplate;
import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.EntryType.*;
import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.Modifier.*;
@@ -61,24 +64,56 @@ public class JavaArrangementVisitor extends JavaRecursiveElementVisitor {
@NotNull private final Collection<TextRange> myRanges;
@NotNull private final Set<ArrangementSettingsToken> myGroupingRules;
@NotNull private final MethodBodyProcessor myMethodBodyProcessor;
@NotNull private final ArrangementSectionDetector mySectionDetector;
@Nullable private final Document myDocument;
@Nullable private Set<PsiField> classFields;
@NotNull private Set<PsiComment> myProcessedSectionsComments = ContainerUtil.newHashSet();
public JavaArrangementVisitor(@NotNull JavaArrangementParseInfo infoHolder,
@Nullable Document document,
@NotNull Collection<TextRange> ranges,
@NotNull Set<ArrangementSettingsToken> groupingRules)
@NotNull ArrangementSettings settings)
{
myInfo = infoHolder;
myDocument = document;
myRanges = ranges;
myGroupingRules = groupingRules;
myGroupingRules = getGroupingRules(settings);
myMethodBodyProcessor = new MethodBodyProcessor(infoHolder);
mySectionDetector = new ArrangementSectionDetector(document, settings, new Consumer<ArrangementSectionEntryTemplate>() {
@Override
public void consume(ArrangementSectionEntryTemplate data) {
TextRange range = data.getTextRange();
JavaSectionArrangementEntry entry = new JavaSectionArrangementEntry(getCurrent(), data.getToken(), range, data.getText(), true);
registerEntry(data.getElement(), entry);
}
});
}
@Override
public void visitComment(PsiComment comment) {
if (myProcessedSectionsComments.contains(comment)) {
return;
}
mySectionDetector.processComment(comment);
}
@NotNull
private static Set<ArrangementSettingsToken> getGroupingRules(@NotNull ArrangementSettings settings) {
Set<ArrangementSettingsToken> groupingRules = ContainerUtilRt.newHashSet();
for (ArrangementGroupingRule rule : settings.getGroupings()) {
groupingRules.add(rule.getGroupingType());
}
return groupingRules;
}
@Override
public void visitClass(PsiClass aClass) {
boolean isSectionCommentsDetected = registerSectionComments(aClass);
TextRange range = isSectionCommentsDetected ? getElementRangeWithoutComments(aClass) : aClass.getTextRange();
ArrangementSettingsToken type = CLASS;
if (aClass.isEnum()) {
type = ENUM;
@@ -86,7 +121,7 @@ public class JavaArrangementVisitor extends JavaRecursiveElementVisitor {
else if (aClass.isInterface()) {
type = INTERFACE;
}
JavaElementArrangementEntry entry = createNewEntry(aClass, aClass.getTextRange(), type, aClass.getName(), true);
JavaElementArrangementEntry entry = createNewEntry(aClass, range, type, aClass.getName(), true);
processEntry(entry, aClass, aClass);
}
@@ -104,6 +139,8 @@ public class JavaArrangementVisitor extends JavaRecursiveElementVisitor {
@Override
public void visitField(PsiField field) {
boolean isSectionCommentsDetected = registerSectionComments(field);
// There is a possible case that more than one field is declared for the same type like 'int i, j;'. We want to process only
// the first one then.
PsiElement fieldPrev = getPreviousNonWsComment(field.getPrevSibling(), 0);
@@ -115,7 +152,7 @@ public class JavaArrangementVisitor extends JavaRecursiveElementVisitor {
// int i1,
// i2;
// We want to consider only the first declaration then but need to expand its range to all affected lines (up to semicolon).
TextRange range = field.getTextRange();
TextRange range = isSectionCommentsDetected ? getElementRangeWithoutComments(field) : field.getTextRange();
PsiElement child = field.getLastChild();
boolean needSpecialProcessing = true;
if (isSemicolon(child)) {
@@ -128,7 +165,7 @@ public class JavaArrangementVisitor extends JavaRecursiveElementVisitor {
PsiElement prev = getPreviousNonWsComment(child, range.getStartOffset());
needSpecialProcessing = prev != null && !isSemicolon(prev);
}
if (needSpecialProcessing) {
for (PsiElement e = field.getNextSibling(); e != null; e = e.getNextSibling()) {
if (e instanceof PsiWhiteSpace || e instanceof PsiComment) { // Skip white space and comment
@@ -283,14 +320,48 @@ public class JavaArrangementVisitor extends JavaRecursiveElementVisitor {
}
}
@NotNull
public static TextRange getElementRangeWithoutComments(@NotNull PsiElement element) {
PsiElement[] children = element.getChildren();
assert(children.length > 1 && children[0] instanceof PsiComment);
int i = 0;
PsiElement child = children[i];
while (child instanceof PsiWhiteSpace || child instanceof PsiComment) {
child = children[++i];
}
return new TextRange(child.getTextRange().getStartOffset(), element.getTextRange().getEndOffset());
}
@NotNull
public static List<PsiComment> getComments(@NotNull PsiElement element) {
PsiElement[] children = element.getChildren();
List<PsiComment> comments = ContainerUtil.newArrayList();
for (PsiElement e : children) {
if (e instanceof PsiComment) {
comments.add((PsiComment)e);
} else if (!(e instanceof PsiWhiteSpace)) {
return comments;
}
}
return comments;
}
@Override
public void visitMethod(PsiMethod method) {
boolean isSectionCommentsDetected = registerSectionComments(method);
final TextRange range = isSectionCommentsDetected ? getElementRangeWithoutComments(method)
: method.getTextRange();
ArrangementSettingsToken type = method.isConstructor() ? CONSTRUCTOR : METHOD;
JavaElementArrangementEntry entry = createNewEntry(method, method.getTextRange(), type, method.getName(), true);
JavaElementArrangementEntry entry = createNewEntry(method, range, type, method.getName(), true);
if (entry == null) {
return;
}
processEntry(entry, method, method.getBody());
parseProperties(method, entry);
myInfo.onMethodEntryCreated(method, entry);
@@ -309,6 +380,18 @@ public class JavaArrangementVisitor extends JavaRecursiveElementVisitor {
}
}
private boolean registerSectionComments(@NotNull PsiElement element) {
List<PsiComment> comments = getComments(element);
boolean isSectionCommentsDetected = false;
for (PsiComment comment : comments) {
if (mySectionDetector.processComment(comment)) {
isSectionCommentsDetected = true;
myProcessedSectionsComments.add(comment);
}
}
return isSectionCommentsDetected;
}
private void parseProperties(PsiMethod method, JavaElementArrangementEntry entry) {
if (!myGroupingRules.contains(StdArrangementTokens.Grouping.GETTERS_AND_SETTERS)) {
return;
@@ -370,7 +453,18 @@ public class JavaArrangementVisitor extends JavaRecursiveElementVisitor {
myStack.pop();
}
}
private void registerEntry(@NotNull PsiElement element, @NotNull JavaElementArrangementEntry entry) {
myEntries.put(element, entry);
DefaultArrangementEntry current = getCurrent();
if (current == null) {
myInfo.addEntry(entry);
}
else {
current.addChild(entry);
}
}
@Nullable
private JavaElementArrangementEntry createNewEntry(@NotNull PsiElement element,
@NotNull TextRange range,
@@ -391,14 +485,7 @@ public class JavaArrangementVisitor extends JavaRecursiveElementVisitor {
else {
entry = new JavaElementArrangementEntry(current, range, type, name, false);
}
myEntries.put(element, entry);
if (current == null) {
myInfo.addEntry(entry);
}
else {
current.addChild(entry);
}
registerEntry(element, entry);
return entry;
}
@@ -433,7 +520,7 @@ public class JavaArrangementVisitor extends JavaRecursiveElementVisitor {
entry.addModifier(PACKAGE_PRIVATE);
}
}
private static class MethodBodyProcessor extends JavaRecursiveElementVisitor {
@NotNull private final JavaArrangementParseInfo myInfo;
@@ -55,7 +55,10 @@ import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.Ge
* @author Denis Zhdanov
* @since 7/20/12 2:31 PM
*/
public class JavaRearranger implements Rearranger<JavaElementArrangementEntry>, ArrangementStandardSettingsAware, ArrangementColorsAware {
public class JavaRearranger implements Rearranger<JavaElementArrangementEntry>,
ArrangementSectionRuleAwareSettings,
ArrangementStandardSettingsAware,
ArrangementColorsAware {
// Type
@NotNull private static final Set<ArrangementSettingsToken> SUPPORTED_TYPES =
@@ -215,12 +218,11 @@ public class JavaRearranger implements Rearranger<JavaElementArrangementEntry>,
@NotNull PsiElement element,
@NotNull ArrangementSettings settings)
{
Set<ArrangementSettingsToken> groupingRules = getGroupingRules(settings);
JavaArrangementParseInfo existingEntriesInfo = new JavaArrangementParseInfo();
root.accept(new JavaArrangementVisitor(existingEntriesInfo, document, ranges, groupingRules));
root.accept(new JavaArrangementVisitor(existingEntriesInfo, document, ranges, settings));
JavaArrangementParseInfo newEntryInfo = new JavaArrangementParseInfo();
element.accept(new JavaArrangementVisitor(newEntryInfo, document, Collections.singleton(element.getTextRange()), groupingRules));
element.accept(new JavaArrangementVisitor(newEntryInfo, document, Collections.singleton(element.getTextRange()), settings));
if (newEntryInfo.getEntries().size() != 1) {
return null;
}
@@ -236,7 +238,7 @@ public class JavaRearranger implements Rearranger<JavaElementArrangementEntry>,
{
// Following entries are subject to arrangement: class, interface, field, method.
JavaArrangementParseInfo parseInfo = new JavaArrangementParseInfo();
root.accept(new JavaArrangementVisitor(parseInfo, document, ranges, getGroupingRules(settings)));
root.accept(new JavaArrangementVisitor(parseInfo, document, ranges, settings));
for (ArrangementGroupingRule rule : settings.getGroupings()) {
if (GETTERS_AND_SETTERS.equals(rule.getGroupingType())) {
setupGettersAndSetters(parseInfo);
@@ -298,17 +300,6 @@ public class JavaRearranger implements Rearranger<JavaElementArrangementEntry>,
}
}
@NotNull
private static Set<ArrangementSettingsToken> getGroupingRules(@Nullable ArrangementSettings settings) {
Set<ArrangementSettingsToken> groupingRules = ContainerUtilRt.newHashSet();
if (settings != null) {
for (ArrangementGroupingRule rule : settings.getGroupings()) {
groupingRules.add(rule.getGroupingType());
}
}
return groupingRules;
}
@NotNull
@Override
public ArrangementSettingsSerializer getSerializer() {
@@ -341,7 +332,7 @@ public class JavaRearranger implements Rearranger<JavaElementArrangementEntry>,
new CompositeArrangementSettingsToken(ORDER, KEEP, BY_NAME)
);
}
@Override
public boolean isEnabled(@NotNull ArrangementSettingsToken token, @Nullable ArrangementMatchCondition current) {
if (SUPPORTED_TYPES.contains(token) || SUPPORTED_ORDERS.contains(token) || StdArrangementTokens.Regexp.NAME.equals(token)) {
@@ -411,11 +402,11 @@ public class JavaRearranger implements Rearranger<JavaElementArrangementEntry>,
if (attributes == null) {
continue;
}
if (result == null) {
result = attributes;
}
Color currentForegroundColor = result.getForegroundColor();
if (currentForegroundColor == null) {
result.setForegroundColor(attributes.getForegroundColor());
@@ -434,7 +425,7 @@ public class JavaRearranger implements Rearranger<JavaElementArrangementEntry>,
if (result != null && result.getForegroundColor() == null) {
return null;
}
if (result != null && result.getBackgroundColor() == null) {
result.setBackgroundColor(scheme.getDefaultBackground());
}
@@ -0,0 +1,41 @@
/*
* Copyright 2000-2014 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.psi.codeStyle.arrangement;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.codeStyle.arrangement.std.ArrangementSettingsToken;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class JavaSectionArrangementEntry extends JavaElementArrangementEntry implements TextAwareArrangementEntry {
@NotNull private final String myText;
public JavaSectionArrangementEntry(@Nullable ArrangementEntry parent,
@NotNull ArrangementSettingsToken type,
@NotNull TextRange range,
@NotNull String text,
boolean canBeMatched)
{
super(parent, range.getStartOffset(), range.getEndOffset(), type, "SECTION", canBeMatched);
myText = text;
}
@NotNull
@Override
public String getText() {
return myText;
}
}