blob: 9e23eadc3073fddff7c7b7deb8e9aa92dcaa92a2 [file] [log] [blame]
/*
* 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.
*/
package com.intellij.psi.codeStyle.arrangement;
import com.intellij.ide.highlighter.JavaHighlightingColors;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.codeStyle.arrangement.engine.ArrangementEngine;
import com.intellij.psi.codeStyle.arrangement.group.ArrangementGroupingRule;
import com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryMatcher;
import com.intellij.psi.codeStyle.arrangement.match.StdArrangementEntryMatcher;
import com.intellij.psi.codeStyle.arrangement.match.StdArrangementMatchRule;
import com.intellij.psi.codeStyle.arrangement.model.ArrangementAtomMatchCondition;
import com.intellij.psi.codeStyle.arrangement.model.ArrangementCompositeMatchCondition;
import com.intellij.psi.codeStyle.arrangement.model.ArrangementMatchCondition;
import com.intellij.psi.codeStyle.arrangement.std.*;
import com.intellij.util.containers.ContainerUtilRt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.*;
import java.util.List;
import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.EntryType.*;
import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.General.*;
import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.Grouping.*;
import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.Modifier.*;
import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.Order.*;
/**
* @author Denis Zhdanov
* @since 7/20/12 2:31 PM
*/
public class JavaRearranger implements Rearranger<JavaElementArrangementEntry>,
ArrangementSectionRuleAwareSettings,
ArrangementStandardSettingsAware,
ArrangementColorsAware {
// Type
@NotNull private static final Set<ArrangementSettingsToken> SUPPORTED_TYPES =
ContainerUtilRt.newLinkedHashSet(
FIELD, CONSTRUCTOR, METHOD, CLASS, INTERFACE, ENUM
);
// Modifier
@NotNull private static final Set<ArrangementSettingsToken> SUPPORTED_MODIFIERS =
ContainerUtilRt.newLinkedHashSet(
PUBLIC, PROTECTED, PACKAGE_PRIVATE, PRIVATE, STATIC, FINAL, ABSTRACT, SYNCHRONIZED, TRANSIENT, VOLATILE
);
@NotNull private static final List<ArrangementSettingsToken> SUPPORTED_ORDERS =
ContainerUtilRt.newArrayList(KEEP, BY_NAME);
@NotNull private static final ArrangementSettingsToken NO_TYPE =
new ArrangementSettingsToken("NO_TYPE", "NO_TYPE");
@NotNull
private static final Map<ArrangementSettingsToken, Set<ArrangementSettingsToken>> MODIFIERS_BY_TYPE =
ContainerUtilRt.newHashMap();
@NotNull private static final Collection<Set<ArrangementSettingsToken>> MUTEXES =
ContainerUtilRt.newArrayList();
static {
Set<ArrangementSettingsToken> visibilityModifiers = ContainerUtilRt.newHashSet(PUBLIC, PROTECTED, PACKAGE_PRIVATE, PRIVATE);
MUTEXES.add(visibilityModifiers);
MUTEXES.add(SUPPORTED_TYPES);
Set<ArrangementSettingsToken> commonModifiers = concat(visibilityModifiers, STATIC, FINAL);
MODIFIERS_BY_TYPE.put(NO_TYPE, commonModifiers);
MODIFIERS_BY_TYPE.put(ENUM, visibilityModifiers);
MODIFIERS_BY_TYPE.put(INTERFACE, visibilityModifiers);
MODIFIERS_BY_TYPE.put(CLASS, concat(commonModifiers, ABSTRACT));
MODIFIERS_BY_TYPE.put(METHOD, concat(commonModifiers, SYNCHRONIZED, ABSTRACT));
MODIFIERS_BY_TYPE.put(CONSTRUCTOR, concat(commonModifiers, SYNCHRONIZED));
MODIFIERS_BY_TYPE.put(FIELD, concat(commonModifiers, TRANSIENT, VOLATILE));
}
private static final Map<ArrangementSettingsToken, List<ArrangementSettingsToken>> GROUPING_RULES = ContainerUtilRt.newLinkedHashMap();
static {
GROUPING_RULES.put(GETTERS_AND_SETTERS, Collections.<ArrangementSettingsToken>emptyList());
GROUPING_RULES.put(OVERRIDDEN_METHODS, ContainerUtilRt.newArrayList(BY_NAME, KEEP));
GROUPING_RULES.put(DEPENDENT_METHODS, ContainerUtilRt.newArrayList(BREADTH_FIRST, DEPTH_FIRST));
}
private static final StdArrangementSettings DEFAULT_SETTINGS;
static {
List<ArrangementGroupingRule> groupingRules = ContainerUtilRt.newArrayList(new ArrangementGroupingRule(GETTERS_AND_SETTERS));
List<StdArrangementMatchRule> matchRules = ContainerUtilRt.newArrayList();
ArrangementSettingsToken[] visibility = {PUBLIC, PROTECTED, PACKAGE_PRIVATE, PRIVATE};
for (ArrangementSettingsToken modifier : visibility) {
and(matchRules, FIELD, STATIC, FINAL, modifier);
}
for (ArrangementSettingsToken modifier : visibility) {
and(matchRules, FIELD, STATIC, modifier);
}
for (ArrangementSettingsToken modifier : visibility) {
and(matchRules, FIELD, FINAL, modifier);
}
for (ArrangementSettingsToken modifier : visibility) {
and(matchRules, FIELD, modifier);
}
and(matchRules, FIELD);
and(matchRules, CONSTRUCTOR);
and(matchRules, METHOD, STATIC);
and(matchRules, METHOD);
and(matchRules, ENUM);
and(matchRules, INTERFACE);
and(matchRules, CLASS, STATIC);
and(matchRules, CLASS);
DEFAULT_SETTINGS = StdArrangementSettings.createByMatchRules(groupingRules, matchRules);
}
private static final DefaultArrangementSettingsSerializer SETTINGS_SERIALIZER = new DefaultArrangementSettingsSerializer(DEFAULT_SETTINGS);
@NotNull
private static Set<ArrangementSettingsToken> concat(@NotNull Set<ArrangementSettingsToken> base, ArrangementSettingsToken... modifiers) {
Set<ArrangementSettingsToken> result = ContainerUtilRt.newHashSet(base);
Collections.addAll(result, modifiers);
return result;
}
private static void setupGettersAndSetters(@NotNull JavaArrangementParseInfo info) {
Collection<JavaArrangementPropertyInfo> properties = info.getProperties();
for (JavaArrangementPropertyInfo propertyInfo : properties) {
JavaElementArrangementEntry getter = propertyInfo.getGetter();
JavaElementArrangementEntry setter = propertyInfo.getSetter();
if (getter != null && setter != null && setter.getDependencies() == null) {
setter.addDependency(getter);
}
}
}
private static void setupUtilityMethods(@NotNull JavaArrangementParseInfo info, @NotNull ArrangementSettingsToken orderType) {
if (DEPTH_FIRST.equals(orderType)) {
for (ArrangementEntryDependencyInfo rootInfo : info.getMethodDependencyRoots()) {
setupDepthFirstDependency(rootInfo);
}
}
else if (BREADTH_FIRST.equals(orderType)) {
for (ArrangementEntryDependencyInfo rootInfo : info.getMethodDependencyRoots()) {
setupBreadthFirstDependency(rootInfo);
}
}
else {
assert false : orderType;
}
}
private static void setupDepthFirstDependency(@NotNull ArrangementEntryDependencyInfo info) {
for (ArrangementEntryDependencyInfo dependencyInfo : info.getDependentEntriesInfos()) {
setupDepthFirstDependency(dependencyInfo);
JavaElementArrangementEntry dependentEntry = dependencyInfo.getAnchorEntry();
if (dependentEntry.getDependencies() == null) {
dependentEntry.addDependency(info.getAnchorEntry());
}
}
}
private static void setupBreadthFirstDependency(@NotNull ArrangementEntryDependencyInfo info) {
Deque<ArrangementEntryDependencyInfo> toProcess = new ArrayDeque<ArrangementEntryDependencyInfo>();
toProcess.add(info);
JavaElementArrangementEntry prev = info.getAnchorEntry();
while (!toProcess.isEmpty()) {
ArrangementEntryDependencyInfo current = toProcess.removeFirst();
for (ArrangementEntryDependencyInfo dependencyInfo : current.getDependentEntriesInfos()) {
JavaElementArrangementEntry dependencyMethod = dependencyInfo.getAnchorEntry();
if (dependencyMethod.getDependencies() == null) {
dependencyMethod.addDependency(prev);
prev = dependencyMethod;
}
toProcess.addLast(dependencyInfo);
}
}
}
private static void setupOverriddenMethods(JavaArrangementParseInfo info) {
for (JavaArrangementOverriddenMethodsInfo methodsInfo : info.getOverriddenMethods()) {
JavaElementArrangementEntry previous = null;
for (JavaElementArrangementEntry entry : methodsInfo.getMethodEntries()) {
if (previous != null && entry.getDependencies() == null) {
entry.addDependency(previous);
}
previous = entry;
}
}
}
@Nullable
@Override
public Pair<JavaElementArrangementEntry, List<JavaElementArrangementEntry>> parseWithNew(
@NotNull PsiElement root,
@Nullable Document document,
@NotNull Collection<TextRange> ranges,
@NotNull PsiElement element,
@NotNull ArrangementSettings settings)
{
JavaArrangementParseInfo existingEntriesInfo = new JavaArrangementParseInfo();
root.accept(new JavaArrangementVisitor(existingEntriesInfo, document, ranges, settings));
JavaArrangementParseInfo newEntryInfo = new JavaArrangementParseInfo();
element.accept(new JavaArrangementVisitor(newEntryInfo, document, Collections.singleton(element.getTextRange()), settings));
if (newEntryInfo.getEntries().size() != 1) {
return null;
}
return Pair.create(newEntryInfo.getEntries().get(0), existingEntriesInfo.getEntries());
}
@NotNull
@Override
public List<JavaElementArrangementEntry> parse(@NotNull PsiElement root,
@Nullable Document document,
@NotNull Collection<TextRange> ranges,
@NotNull ArrangementSettings settings)
{
// Following entries are subject to arrangement: class, interface, field, method.
JavaArrangementParseInfo parseInfo = new JavaArrangementParseInfo();
root.accept(new JavaArrangementVisitor(parseInfo, document, ranges, settings));
for (ArrangementGroupingRule rule : settings.getGroupings()) {
if (GETTERS_AND_SETTERS.equals(rule.getGroupingType())) {
setupGettersAndSetters(parseInfo);
}
else if (DEPENDENT_METHODS.equals(rule.getGroupingType())) {
setupUtilityMethods(parseInfo, rule.getOrderType());
}
else if (OVERRIDDEN_METHODS.equals(rule.getGroupingType())) {
setupOverriddenMethods(parseInfo);
}
}
List<ArrangementEntryDependencyInfo> fieldDependencyRoots = parseInfo.getFieldDependencyRoots();
if (!fieldDependencyRoots.isEmpty()) {
setupFieldInitializationDependencies(fieldDependencyRoots, settings, parseInfo);
}
return parseInfo.getEntries();
}
public void setupFieldInitializationDependencies(@NotNull List<ArrangementEntryDependencyInfo> fieldDependencyRoots,
@NotNull ArrangementSettings settings,
@NotNull JavaArrangementParseInfo parseInfo)
{
Collection<JavaElementArrangementEntry> fields = parseInfo.getFields();
List<JavaElementArrangementEntry> arrangedFields = ArrangementEngine.arrange(fields, settings.getSections(), settings.getRulesSortedByPriority(), null);
for (ArrangementEntryDependencyInfo root : fieldDependencyRoots) {
JavaElementArrangementEntry anchorField = root.getAnchorEntry();
final int anchorEntryIndex = arrangedFields.indexOf(anchorField);
for (ArrangementEntryDependencyInfo fieldInInitializerInfo : root.getDependentEntriesInfos()) {
JavaElementArrangementEntry fieldInInitializer = fieldInInitializerInfo.getAnchorEntry();
if (arrangedFields.indexOf(fieldInInitializer) > anchorEntryIndex) {
anchorField.addDependency(fieldInInitializer);
}
}
}
}
@Override
public int getBlankLines(@NotNull CodeStyleSettings settings,
@Nullable JavaElementArrangementEntry parent,
@Nullable JavaElementArrangementEntry previous,
@NotNull JavaElementArrangementEntry target)
{
if (previous == null) {
return -1;
}
CommonCodeStyleSettings commonSettings = settings.getCommonSettings(JavaLanguage.INSTANCE);
if (FIELD.equals(target.getType())) {
if (parent != null && parent.getType() == INTERFACE) {
return commonSettings.BLANK_LINES_AROUND_FIELD_IN_INTERFACE;
}
else {
return commonSettings.BLANK_LINES_AROUND_FIELD;
}
}
else if (METHOD.equals(target.getType())) {
if (parent != null && parent.getType() == INTERFACE) {
return commonSettings.BLANK_LINES_AROUND_METHOD_IN_INTERFACE;
}
else {
return commonSettings.BLANK_LINES_AROUND_METHOD;
}
}
else if (CLASS.equals(target.getType())) {
return commonSettings.BLANK_LINES_AROUND_CLASS;
}
else {
return -1;
}
}
@NotNull
@Override
public ArrangementSettingsSerializer getSerializer() {
return SETTINGS_SERIALIZER;
}
@NotNull
@Override
public StdArrangementSettings getDefaultSettings() {
return DEFAULT_SETTINGS;
}
@Nullable
@Override
public List<CompositeArrangementSettingsToken> getSupportedGroupingTokens() {
return ContainerUtilRt.newArrayList(
new CompositeArrangementSettingsToken(GETTERS_AND_SETTERS),
new CompositeArrangementSettingsToken(OVERRIDDEN_METHODS, BY_NAME, KEEP),
new CompositeArrangementSettingsToken(DEPENDENT_METHODS, BREADTH_FIRST, DEPTH_FIRST)
);
}
@Nullable
@Override
public List<CompositeArrangementSettingsToken> getSupportedMatchingTokens() {
return ContainerUtilRt.newArrayList(
new CompositeArrangementSettingsToken(TYPE, SUPPORTED_TYPES),
new CompositeArrangementSettingsToken(MODIFIER, SUPPORTED_MODIFIERS),
new CompositeArrangementSettingsToken(StdArrangementTokens.Regexp.NAME),
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)) {
return true;
}
ArrangementSettingsToken type = null;
if (current != null) {
type = ArrangementUtil.parseType(current);
}
if (type == null) {
type = NO_TYPE;
}
Set<ArrangementSettingsToken> modifiers = MODIFIERS_BY_TYPE.get(type);
return modifiers != null && modifiers.contains(token);
}
@NotNull
@Override
public ArrangementEntryMatcher buildMatcher(@NotNull ArrangementMatchCondition condition) throws IllegalArgumentException {
throw new IllegalArgumentException("Can't build a matcher for condition " + condition);
}
@NotNull
@Override
public Collection<Set<ArrangementSettingsToken>> getMutexes() {
return MUTEXES;
}
private static void and(@NotNull List<StdArrangementMatchRule> matchRules, @NotNull ArrangementSettingsToken... conditions) {
if (conditions.length == 1) {
matchRules.add(new StdArrangementMatchRule(new StdArrangementEntryMatcher(new ArrangementAtomMatchCondition(
conditions[0], conditions[0]
))));
return;
}
ArrangementCompositeMatchCondition composite = new ArrangementCompositeMatchCondition();
for (ArrangementSettingsToken condition : conditions) {
composite.addOperand(new ArrangementAtomMatchCondition(condition, condition));
}
matchRules.add(new StdArrangementMatchRule(new StdArrangementEntryMatcher(composite)));
}
@Nullable
@Override
public TextAttributes getTextAttributes(@NotNull EditorColorsScheme scheme, @NotNull ArrangementSettingsToken token, boolean selected) {
if (selected) {
TextAttributes attributes = new TextAttributes();
attributes.setForegroundColor(scheme.getColor(EditorColors.SELECTION_FOREGROUND_COLOR));
attributes.setBackgroundColor(scheme.getColor(EditorColors.SELECTION_BACKGROUND_COLOR));
return attributes;
}
else if (SUPPORTED_TYPES.contains(token)) {
return getAttributes(scheme, JavaHighlightingColors.KEYWORD);
}
else if (SUPPORTED_MODIFIERS.contains(token)) {
getAttributes(scheme, JavaHighlightingColors.KEYWORD);
}
return null;
}
@Nullable
private static TextAttributes getAttributes(@NotNull EditorColorsScheme scheme, @NotNull TextAttributesKey ... keys) {
TextAttributes result = null;
for (TextAttributesKey key : keys) {
TextAttributes attributes = scheme.getAttributes(key);
if (attributes == null) {
continue;
}
if (result == null) {
result = attributes;
}
Color currentForegroundColor = result.getForegroundColor();
if (currentForegroundColor == null) {
result.setForegroundColor(attributes.getForegroundColor());
}
Color currentBackgroundColor = result.getBackgroundColor();
if (currentBackgroundColor == null) {
result.setBackgroundColor(attributes.getBackgroundColor());
}
if (result.getForegroundColor() != null && result.getBackgroundColor() != null) {
return result;
}
}
if (result != null && result.getForegroundColor() == null) {
return null;
}
if (result != null && result.getBackgroundColor() == null) {
result.setBackgroundColor(scheme.getDefaultBackground());
}
return result;
}
@Nullable
@Override
public Color getBorderColor(@NotNull EditorColorsScheme scheme, boolean selected) {
return null;
}
}