/* * 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.*; import static com.intellij.psi.codeStyle.arrangement.std.StdArrangementTokens.Modifier.*; /** * @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} 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<>(); condition.invite(new ArrangementMatchConditionVisitor() { @Override public void visit(@NotNull ArrangementAtomMatchCondition condition) { ArrangementSettingsToken type = condition.getType(); if (StdArrangementTokenType.ENTRY_TYPE.is(condition.getType()) || MODIFIER_AS_TYPE.contains(type)) { 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); if (type instanceof CompositeArrangementToken) { Set<ArrangementSettingsToken> tokens = ((CompositeArrangementToken)type).getAdditionalTokens(); for (ArrangementSettingsToken token : tokens) { result.put(token, null); } } } @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<>(); final Stack<CompositeArrangementEntryMatcher> composites = new Stack<>(); 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); } else if (StdArrangementTokenType.MODIFIER.is(condition.getType())) { return new ByModifierArrangementEntryMatcher(condition); } 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 //region Arrangement Custom Tokens public static List<ArrangementSectionRule> getExtendedSectionRules(@NotNull ArrangementSettings settings) { return settings instanceof ArrangementExtendableSettings ? ((ArrangementExtendableSettings)settings).getExtendedSectionRules() : settings.getSections(); } public static boolean isAliasedCondition(@NotNull ArrangementAtomMatchCondition condition) { return StdArrangementTokenType.ALIAS.is(condition.getType()); } //endregion }