blob: 2bc92f71fe942a17fdd085960f714dc1d7920a14 [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.application.options.codeStyle.arrangement.color.ArrangementColorsProvider;
import com.intellij.lang.Language;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.codeStyle.arrangement.match.*;
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.model.ArrangementMatchConditionVisitor;
import com.intellij.psi.codeStyle.arrangement.std.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.ContainerUtilRt;
import com.intellij.util.text.CharArrayUtil;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author Denis Zhdanov
* @since 7/17/12 11:24 AM
*/
public class ArrangementUtil {
private static final Logger LOG = Logger.getInstance(ArrangementUtil.class);
private ArrangementUtil() {
}
//region Serialization
@Nullable
public static ArrangementSettings readExternal(@NotNull Element element, @NotNull Language language) {
ArrangementSettingsSerializer serializer = getSerializer(language);
if (serializer == null) {
LOG.error("Can't find serializer for language: " + language.getDisplayName() + "(" + language.getID() + ")");
return null;
}
return serializer.deserialize(element);
}
public static void writeExternal(@NotNull Element element, @NotNull ArrangementSettings settings, @NotNull Language language) {
ArrangementSettingsSerializer serializer = getSerializer(language);
if (serializer == null) {
LOG.error("Can't find serializer for language: " + language.getDisplayName() + "(" + language.getID() + ")");
return;
}
serializer.serialize(settings, element);
}
@Nullable
private static ArrangementSettingsSerializer getSerializer(@NotNull Language language) {
Rearranger<?> rearranger = Rearranger.EXTENSION.forLanguage(language);
return rearranger == null ? null : rearranger.getSerializer();
}
//endregion
@NotNull
public static ArrangementMatchCondition combine(@NotNull ArrangementMatchCondition... nodes) {
final ArrangementCompositeMatchCondition result = new ArrangementCompositeMatchCondition();
final ArrangementMatchConditionVisitor visitor = new ArrangementMatchConditionVisitor() {
@Override
public void visit(@NotNull ArrangementAtomMatchCondition node) {
result.addOperand(node);
}
@Override
public void visit(@NotNull ArrangementCompositeMatchCondition node) {
for (ArrangementMatchCondition operand : node.getOperands()) {
operand.invite(this);
}
}
};
for (ArrangementMatchCondition node : nodes) {
node.invite(visitor);
}
return result.getOperands().size() == 1 ? result.getOperands().iterator().next() : result;
}
//region ArrangementEntry
/**
* Tries to build a text range on the given arguments basis. Expands to the line start/end if possible.
* <p/>
* This method is expected to be used in a situation when we want to arrange complete rows.
* Example:
* <pre>
* class Test {
* void test() {
* }
* int i;
* }
* </pre>
* Suppose, we want to locate fields before methods. We can move the exact field and method range then but indent will be broken,
* i.e. we'll get the result below:
* <pre>
* class Test {
* int i;
* void test() {
* }
* }
* </pre>
* We can expand field and method range to the whole lines and that would allow to achieve the desired result:
* <pre>
* class Test {
* int i;
* void test() {
* }
* }
* </pre>
* However, this method is expected to just return given range if there are multiple distinct elements at the same line:
* <pre>
* class Test {
* void test1(){} void test2() {} int i;
* }
* </pre>
*
* @param initialRange anchor range
* @param document target document against which the ranges are built
* @return expanded range if possible; <code>null</code> otherwise
*/
@NotNull
public static TextRange expandToLineIfPossible(@NotNull TextRange initialRange, @NotNull Document document) {
CharSequence text = document.getCharsSequence();
String ws = " \t";
int startLine = document.getLineNumber(initialRange.getStartOffset());
int lineStartOffset = document.getLineStartOffset(startLine);
int i = CharArrayUtil.shiftBackward(text, lineStartOffset + 1, initialRange.getStartOffset() - 1, ws);
if (i != lineStartOffset) {
return initialRange;
}
int endLine = document.getLineNumber(initialRange.getEndOffset());
int lineEndOffset = document.getLineEndOffset(endLine);
i = CharArrayUtil.shiftForward(text, initialRange.getEndOffset(), lineEndOffset, ws);
return i == lineEndOffset ? TextRange.create(lineStartOffset, lineEndOffset) : initialRange;
}
//endregion
@Nullable
public static ArrangementSettingsToken parseType(@NotNull ArrangementMatchCondition condition) throws IllegalArgumentException {
final Ref<ArrangementSettingsToken> result = new Ref<ArrangementSettingsToken>();
condition.invite(new ArrangementMatchConditionVisitor() {
@Override
public void visit(@NotNull ArrangementAtomMatchCondition condition) {
if (StdArrangementTokenType.ENTRY_TYPE.is(condition.getType())) {
result.set(condition.getType());
}
}
@Override
public void visit(@NotNull ArrangementCompositeMatchCondition condition) {
for (ArrangementMatchCondition c : condition.getOperands()) {
c.invite(this);
if (result.get() != null) {
return;
}
}
}
});
return result.get();
}
public static <T> Set<T> flatten(@NotNull Iterable<? extends Iterable<T>> data) {
Set<T> result = ContainerUtilRt.newHashSet();
for (Iterable<T> i : data) {
for (T t : i) {
result.add(t);
}
}
return result;
}
@NotNull
public static Map<ArrangementSettingsToken, Object> extractTokens(@NotNull ArrangementMatchCondition condition) {
final Map<ArrangementSettingsToken, Object> result = ContainerUtilRt.newHashMap();
condition.invite(new ArrangementMatchConditionVisitor() {
@Override
public void visit(@NotNull ArrangementAtomMatchCondition condition) {
ArrangementSettingsToken type = condition.getType();
Object value = condition.getValue();
result.put(condition.getType(), type.equals(value) ? null : value);
}
@Override
public void visit(@NotNull ArrangementCompositeMatchCondition condition) {
for (ArrangementMatchCondition operand : condition.getOperands()) {
operand.invite(this);
}
}
});
return result;
}
@Nullable
public static ArrangementEntryMatcher buildMatcher(@NotNull ArrangementMatchCondition condition) {
final Ref<ArrangementEntryMatcher> result = new Ref<ArrangementEntryMatcher>();
final Stack<CompositeArrangementEntryMatcher> composites = new Stack<CompositeArrangementEntryMatcher>();
ArrangementMatchConditionVisitor visitor = new ArrangementMatchConditionVisitor() {
@Override
public void visit(@NotNull ArrangementAtomMatchCondition condition) {
ArrangementEntryMatcher matcher = buildMatcher(condition);
if (matcher == null) {
return;
}
if (composites.isEmpty()) {
result.set(matcher);
}
else {
composites.peek().addMatcher(matcher);
}
}
@Override
public void visit(@NotNull ArrangementCompositeMatchCondition condition) {
composites.push(new CompositeArrangementEntryMatcher());
try {
for (ArrangementMatchCondition operand : condition.getOperands()) {
operand.invite(this);
}
}
finally {
CompositeArrangementEntryMatcher matcher = composites.pop();
if (composites.isEmpty()) {
result.set(matcher);
}
}
}
};
condition.invite(visitor);
return result.get();
}
@Nullable
public static ArrangementEntryMatcher buildMatcher(@NotNull ArrangementAtomMatchCondition condition) {
if (StdArrangementTokenType.ENTRY_TYPE.is(condition.getType())) {
return new ByTypeArrangementEntryMatcher(condition.getType());
}
else if (StdArrangementTokenType.MODIFIER.is(condition.getType())) {
return new ByModifierArrangementEntryMatcher(condition.getType());
}
else if (StdArrangementTokens.Regexp.NAME.equals(condition.getType())) {
return new ByNameArrangementEntryMatcher(condition.getValue().toString());
}
else if (StdArrangementTokens.Regexp.XML_NAMESPACE.equals(condition.getType())) {
return new ByNamespaceArrangementEntryMatcher(condition.getValue().toString());
}
else {
return null;
}
}
@NotNull
public static ArrangementUiComponent buildUiComponent(@NotNull StdArrangementTokenUiRole role,
@NotNull List<ArrangementSettingsToken> tokens,
@NotNull ArrangementColorsProvider colorsProvider,
@NotNull ArrangementStandardSettingsManager settingsManager)
throws IllegalArgumentException
{
for (ArrangementUiComponent.Factory factory : Extensions.getExtensions(ArrangementUiComponent.Factory.EP_NAME)) {
ArrangementUiComponent result = factory.build(role, tokens, colorsProvider, settingsManager);
if (result != null) {
return result;
}
}
throw new IllegalArgumentException("Unsupported UI token role " + role);
}
@NotNull
public static List<CompositeArrangementSettingsToken> flatten(@NotNull CompositeArrangementSettingsToken base) {
List<CompositeArrangementSettingsToken> result = ContainerUtilRt.newArrayList();
Queue<CompositeArrangementSettingsToken> toProcess = ContainerUtilRt.newLinkedList(base);
while (!toProcess.isEmpty()) {
CompositeArrangementSettingsToken token = toProcess.remove();
result.add(token);
toProcess.addAll(token.getChildren());
}
return result;
}
//region Arrangement Sections
@NotNull
public static List<StdArrangementMatchRule> collectMatchRules(@NotNull List<ArrangementSectionRule> sections) {
final List<StdArrangementMatchRule> matchRules = ContainerUtil.newArrayList();
for (ArrangementSectionRule section : sections) {
matchRules.addAll(section.getMatchRules());
}
return matchRules;
}
//endregion
}