diff --git a/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaArrangementVisitor.java b/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaArrangementVisitor.java index c14cb2f493df..acbec2534794 100644 --- a/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaArrangementVisitor.java +++ b/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaArrangementVisitor.java @@ -48,11 +48,11 @@ public class JavaArrangementVisitor extends JavaElementVisitor { private final Stack myStack = new Stack(); @NotNull private final List myRootEntries; - @NotNull private Document myDocument; @NotNull private Collection myRanges; + @Nullable private Document myDocument; public JavaArrangementVisitor(@NotNull List entries, - @NotNull Document document, + @Nullable Document document, @NotNull Collection ranges) { myRootEntries = entries; @@ -78,7 +78,7 @@ public class JavaArrangementVisitor extends JavaElementVisitor { JavaElementArrangementEntry entry = createNewEntry(aClass.getTextRange(), ArrangementEntryType.CLASS, aClass.getName(), false); processEntry(entry, null, aClass); } - + @Override public void visitJavaFile(PsiJavaFile file) { for (PsiClass psiClass : file.getClasses()) { @@ -162,9 +162,9 @@ public class JavaArrangementVisitor extends JavaElementVisitor { DefaultArrangementEntry current = getCurrent(); JavaElementArrangementEntry entry; if (canArrange) { - TextRange expandedRange = ArrangementUtil.expandToLine(range, myDocument.getCharsSequence()); + TextRange expandedRange = myDocument == null ? null : ArrangementUtil.expandToLine(range, myDocument.getCharsSequence()); TextRange rangeToUse = expandedRange == null ? range : expandedRange; - entry = new JavaElementArrangementEntry(current, rangeToUse, type, name, expandedRange != null); + entry = new JavaElementArrangementEntry(current, rangeToUse, type, name, myDocument == null || expandedRange != null); } else { entry = new JavaElementArrangementEntry(current, range, type, name, false); diff --git a/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaRearranger.java b/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaRearranger.java index f4b9a342257e..1973008a6d8d 100644 --- a/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaRearranger.java +++ b/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaRearranger.java @@ -114,11 +114,19 @@ public class JavaRearranger implements Rearranger, return result; } + @Nullable + @Override + public JavaElementArrangementEntry wrap(@NotNull PsiElement element) { + List result = new ArrayList(); + element.accept(new JavaArrangementVisitor(result, null, Collections.singleton(element.getTextRange()))); + return result.size() == 1 ? result.get(0) : null; + } + @NotNull @Override - public Collection parse(@NotNull PsiElement root, - @NotNull Document document, - @NotNull Collection ranges) + public List parse(@NotNull PsiElement root, + @Nullable Document document, + @NotNull Collection ranges) { // Following entries are subject to arrangement: class, interface, field, method. List result = new ArrayList(); diff --git a/java/java-impl/src/com/intellij/psi/impl/JavaPsiImplementationHelperImpl.java b/java/java-impl/src/com/intellij/psi/impl/JavaPsiImplementationHelperImpl.java index 5a1b11ae77f2..c3c6775e0c37 100644 --- a/java/java-impl/src/com/intellij/psi/impl/JavaPsiImplementationHelperImpl.java +++ b/java/java-impl/src/com/intellij/psi/impl/JavaPsiImplementationHelperImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * 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. @@ -19,6 +19,8 @@ import com.intellij.ide.fileTemplates.FileTemplate; import com.intellij.ide.fileTemplates.FileTemplateManager; import com.intellij.ide.fileTemplates.JavaTemplateUtil; import com.intellij.lang.ASTNode; +import com.intellij.lang.java.JavaLanguage; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.module.Module; @@ -32,6 +34,7 @@ import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.codeStyle.arrangement.MemberOrderService; import com.intellij.psi.impl.compiled.ClsClassImpl; import com.intellij.psi.impl.source.codeStyle.ImportHelper; import com.intellij.psi.search.GlobalSearchScope; @@ -174,43 +177,37 @@ public class JavaPsiImplementationHelperImpl extends JavaPsiImplementationHelper return importHelper.getDefaultAnchor(list, statement); } + @Nullable @Override - public PsiElement getDefaultMemberAnchor(PsiClass aClass, PsiMember member) { + public PsiElement getDefaultMemberAnchor(@NotNull PsiClass aClass, @NotNull PsiMember member) { CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(aClass.getProject()); - - int order = getMemberOrderWeight(member, settings); - if (order < 0) return null; - - PsiElement lastMember = null; - for (PsiElement child = aClass.getFirstChild(); child != null; child = child.getNextSibling()) { - int order1 = getMemberOrderWeight(child, settings); - if (order1 < 0) continue; - if (order1 > order) { - if (lastMember != null) { - PsiElement nextSibling = lastMember.getNextSibling(); - while (nextSibling instanceof PsiJavaToken && (nextSibling.getText().equals(",") || nextSibling.getText().equals(";"))) { - nextSibling = nextSibling.getNextSibling(); - } - return nextSibling == null ? aClass.getLBrace().getNextSibling() : nextSibling; - } - else { - // 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(). - final PsiElement lBrace = aClass.getLBrace(); - if (lBrace != null) { - PsiElement result = lBrace.getNextSibling(); - while (result instanceof PsiWhiteSpace) { - result = result.getNextSibling(); - } - return result; - } - } + MemberOrderService service = ServiceManager.getService(MemberOrderService.class); + PsiElement anchor = service.getAnchor(member, settings.getCommonSettings(JavaLanguage.INSTANCE), aClass); + + if (anchor != null && anchor != aClass) { + while (anchor instanceof PsiJavaToken && (anchor.getText().equals(",") || anchor.getText().equals(";"))) { + anchor = anchor.getNextSibling(); + } + if (anchor != null) { + return anchor; } - lastMember = child; } + + // 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(). + final PsiElement lBrace = aClass.getLBrace(); + if (lBrace != null) { + PsiElement result = lBrace.getNextSibling(); + while (result instanceof PsiWhiteSpace) { + result = result.getNextSibling(); + } + return result; + } + return aClass.getRBrace(); } - + + // TODO remove as soon as arrangement sub-system is provided for groovy. public static int getMemberOrderWeight(PsiElement member, CodeStyleSettings settings) { if (member instanceof PsiField) { if (member instanceof PsiEnumConstant) { diff --git a/java/java-psi-impl/src/com/intellij/core/CoreJavaPsiImplementationHelper.java b/java/java-psi-impl/src/com/intellij/core/CoreJavaPsiImplementationHelper.java index 0407bc285e35..e78527248038 100644 --- a/java/java-psi-impl/src/com/intellij/core/CoreJavaPsiImplementationHelper.java +++ b/java/java-psi-impl/src/com/intellij/core/CoreJavaPsiImplementationHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * 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. @@ -20,6 +20,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.impl.JavaPsiImplementationHelper; +import org.jetbrains.annotations.NotNull; /** * @author yole @@ -46,7 +47,7 @@ public class CoreJavaPsiImplementationHelper extends JavaPsiImplementationHelper } @Override - public PsiElement getDefaultMemberAnchor(PsiClass psiClass, PsiMember firstPsi) { + public PsiElement getDefaultMemberAnchor(@NotNull PsiClass psiClass, @NotNull PsiMember firstPsi) { throw new UnsupportedOperationException("TODO"); } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/JavaPsiImplementationHelper.java b/java/java-psi-impl/src/com/intellij/psi/impl/JavaPsiImplementationHelper.java index 89030a88f5fb..77b0675b6dde 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/JavaPsiImplementationHelper.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/JavaPsiImplementationHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * 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. @@ -21,6 +21,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** @@ -46,7 +47,8 @@ public abstract class JavaPsiImplementationHelper { public abstract ASTNode getDefaultImportAnchor(PsiImportList list, PsiImportStatementBase statement); - public abstract PsiElement getDefaultMemberAnchor(PsiClass psiClass, PsiMember firstPsi); + @Nullable + public abstract PsiElement getDefaultMemberAnchor(@NotNull PsiClass psiClass, @NotNull PsiMember firstPsi); public abstract void setupCatchBlock(String exceptionName, PsiElement context, PsiCatchSection element); } diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/ArrangementEntry.java b/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/ArrangementEntry.java index d3ad9f9eefee..65c980039688 100644 --- a/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/ArrangementEntry.java +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/ArrangementEntry.java @@ -19,7 +19,7 @@ import com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryMatcher; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Collection; +import java.util.List; /** * Represents a processing unit during 'rearrangement' operation. I.e. entry's position can be changed during the processing. @@ -54,7 +54,7 @@ public interface ArrangementEntry { * @see #getParent() */ @NotNull - Collection getChildren(); + List getChildren(); /** * @return start offset of the current entry (inclusive) within the target document. Rearranger engine uses this information diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/DefaultArrangementEntry.java b/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/DefaultArrangementEntry.java index b580ee928f37..0ffc5878f230 100644 --- a/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/DefaultArrangementEntry.java +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/DefaultArrangementEntry.java @@ -19,7 +19,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; -import java.util.Collection; import java.util.List; /** @@ -51,7 +50,7 @@ public class DefaultArrangementEntry implements ArrangementEntry { @NotNull @Override - public Collection getChildren() { + public List getChildren() { return myChildren; } diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/Rearranger.java b/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/Rearranger.java index 067f1bbe6be6..228d882c826a 100644 --- a/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/Rearranger.java +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/Rearranger.java @@ -24,6 +24,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; +import java.util.List; /** * Encapsulates language-specific rearrangement logic. @@ -38,16 +39,29 @@ public interface Rearranger { LanguageExtension> EXTENSION = new LanguageExtension>("com.intellij.lang.rearranger"); + /** + * Tries to wrap given element to the corresponding arrangement entry. + *

+ * This is useful in a situation when new element is generated and we're deciding where to insert it (e.g. new field is + * generated and we want to insert it according to the arrangement rules like 'fields before methods'). + * + * @param element element to wrap into format eligible for further processing by arrangement engine + * @return arrangement entry for the given element if it's possible to perform the mapping; + * null otherwise + */ + @Nullable + E wrap(@NotNull PsiElement element); + /** * Allows to build rearranger-interested data for the given element. * * @param root root element which children should be parsed for the rearrangement * @param document document which corresponds to the target PSI tree * @param ranges target offsets ranges to use for filtering given root's children - * @return given root's children which are subject for further rearrangement + * @return given root's children which are subject for further rearrangement */ @NotNull - Collection parse(@NotNull PsiElement root, @NotNull Document document, @NotNull Collection ranges); + List parse(@NotNull PsiElement root, @Nullable Document document, @NotNull Collection ranges); /** * Allows to answer how many blank lines should be inserted before the target arrangement entry which position is changed. diff --git a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/DocumentFoldingInfo.java b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/DocumentFoldingInfo.java index ea9abbae8a92..bcd077047657 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/DocumentFoldingInfo.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/folding/impl/DocumentFoldingInfo.java @@ -93,7 +93,7 @@ class DocumentFoldingInfo implements JDOMExternalizable, CodeFoldingState { } } - void setToEditor(@NotNull Editor editor) { + void setToEditor(@NotNull final Editor editor) { ApplicationManager.getApplication().assertIsDispatchThread(); final PsiManager psiManager = PsiManager.getInstance(myProject); if (psiManager.isDisposed()) return; diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/MemberOrderService.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/MemberOrderService.java index e27df2693c96..bbae5db1b6b8 100644 --- a/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/MemberOrderService.java +++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/MemberOrderService.java @@ -15,11 +15,19 @@ */ package com.intellij.psi.codeStyle.arrangement; +import com.intellij.lang.Language; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; +import com.intellij.psi.codeStyle.arrangement.engine.ArrangementEngine; +import com.intellij.psi.codeStyle.arrangement.settings.ArrangementStandardSettingsAware; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + /** * The whole arrangement idea is to allow to change file entries order according to the user-provided rules. *

@@ -33,25 +41,57 @@ import org.jetbrains.annotations.Nullable; */ public class MemberOrderService { - public static final int UNDEFINED_WEIGHT = -1; - /** - * Allows to get a given member's weight according to the + * Tries to find an element at the given context which should be the previous sibling for the given 'member'element according to the * {@link CommonCodeStyleSettings#getArrangementRules() user-defined arrangement rules}. *

- * That means that we can call this method for different members and derive their relative order by comparing their weights. + * E.g. the IDE might generate given 'member' element and wants to know element after which it should be inserted * - * @param member target member which weight should be calculated + * @param member target member which anchor should be calculated * @param settings code style settings to use - * @param context given member's context (if any). Is useful when we have, say, an existing class and want to know where - * to insert a new field. Not only 'by type' filtering might be exploited (like 'fields before methods') but - * 'by name' as well, i.e. we want to insert a new field to the 'fidles' group and define its position - * according to the lexicographical fields order - * @return given member's weight if the one can be computed; - * {@link #UNDEFINED_WEIGHT} otherwise + * @param context given member's context + * @return given member's anchor if the one can be computed; + * given 'context' element if given member should be the first child + * null otherwise */ - public int getMemberOrderWeight(@NotNull PsiElement member, @NotNull CommonCodeStyleSettings settings, @Nullable PsiElement context) { - // TODO den implement - return UNDEFINED_WEIGHT; + @SuppressWarnings("MethodMayBeStatic") + @Nullable + public PsiElement getAnchor(@NotNull PsiElement member, @NotNull CommonCodeStyleSettings settings, @NotNull PsiElement context) { + Language language = context.getLanguage(); + Rearranger rearranger = Rearranger.EXTENSION.forLanguage(language); + if (rearranger == null) { + return null; + } + + List rules = settings.getArrangementRules(); + if (rules.isEmpty() && rearranger instanceof ArrangementStandardSettingsAware) { + rules = ((ArrangementStandardSettingsAware)rearranger).getDefaultRules(); + } + + if (rules == null) { + return null; + } + + ArrangementEntry memberEntry = rearranger.wrap(member); + if (memberEntry == null) { + return null; + } + + List entries = rearranger.parse(context, null, Collections.singleton(context.getTextRange())); + if (entries.isEmpty()) { + return null; + } + + ArrangementEntry parentEntry = entries.get(0); + List entriesWithNew = new ArrayList(parentEntry.getChildren()); + entriesWithNew.add(memberEntry); + List arranged = ArrangementEngine.arrange(entriesWithNew, rules); + int i = arranged.indexOf(memberEntry); + if (i <= 0) { + return context; + } + + ArrangementEntry anchorEntry = arranged.get(i - 1); + return context.findElementAt(anchorEntry.getEndOffset() - context.getTextRange().getStartOffset()); } } diff --git a/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementEngine.java b/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementEngine.java index d570fe9b4360..52642fdf5414 100644 --- a/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementEngine.java +++ b/platform/lang-impl/src/com/intellij/psi/codeStyle/arrangement/engine/ArrangementEngine.java @@ -192,29 +192,52 @@ public class ArrangementEngine { } } - @SuppressWarnings("unchecked") - private static void doArrange(@NotNull List> entries, - @NotNull Context context) + /** + * Arranges (re-orders) given entries according to the given rules. + * + * @param entries entries to arrange + * @param rules rules to use for arrangement + * @param arrangement entry type + * @return arranged list of the given rules + */ + @NotNull + public static List arrange(@NotNull Collection entries, + @NotNull List rules) { - List> arranged = new ArrayList>(); - Set> unprocessed = new LinkedHashSet>(entries); + List arranged = new ArrayList(); + Set unprocessed = new LinkedHashSet(entries); - for (ArrangementRule rule : context.rules) { - for (ArrangementEntryWrapper wrapper : entries) { - if (wrapper.getEntry().canBeMatched() && unprocessed.contains(wrapper) && rule.getMatcher().isMatched(wrapper.getEntry())) { - arranged.add(wrapper); - unprocessed.remove(wrapper); + for (ArrangementRule rule : rules) { + for (E entry : entries) { + if (entry.canBeMatched() && unprocessed.contains(entry) && rule.getMatcher().isMatched(entry)) { + arranged.add(entry); + unprocessed.remove(entry); } } } arranged.addAll(unprocessed); - - context.prepare(arranged); + return arranged; + } + + @SuppressWarnings("unchecked") + private static void doArrange(@NotNull List> wrappers, + @NotNull Context context) { + if (wrappers.isEmpty()) { + return; + } + Map> map = new LinkedHashMap>(); + for (ArrangementEntryWrapper wrapper : wrappers) { + map.put(wrapper.getEntry(), wrapper); + } + List arranged = arrange(map.keySet(), context.rules); + + + context.prepare(wrappers.get(0).getParent()); // We apply changes from the last position to the first position in order not to bother with offsets shifts. for (int i = arranged.size() - 1; i >= 0; i--) { - ArrangementEntryWrapper arrangedWrapper = arranged.get(i); - ArrangementEntryWrapper initialWrapper = entries.get(i); - context.replace(arrangedWrapper, initialWrapper, i > 0 ? arranged.get(i - 1) : null); + ArrangementEntryWrapper arrangedWrapper = map.get(arranged.get(i)); + ArrangementEntryWrapper initialWrapper = wrappers.get(i); + context.replace(arrangedWrapper, initialWrapper, i > 0 ? map.get(arranged.get(i - 1)) : null); } } @@ -264,11 +287,7 @@ public class ArrangementEngine { return new Context(rearranger, wrappers, document, rules, settings); } - public void prepare(@NotNull List> arrangedEntries) { - if (arrangedEntries.isEmpty()) { - return; - } - ArrangementEntryWrapper parent = arrangedEntries.get(0).getParent(); + public void prepare(@Nullable ArrangementEntryWrapper parent) { if (parent == null) { myParentText = document.getText(); myParentShift = 0;