From 16d974531d42838c797be94db4360be088ef571d Mon Sep 17 00:00:00 2001 From: Konstantin Bulenkov Date: Wed, 9 May 2012 17:22:41 +0200 Subject: [PATCH 01/31] groups for related files popup --- .../intellij/navigation/GotoRelatedItem.java | 27 +++++-- .../ide/actions/GotoRelatedFileAction.java | 81 +++++++++++++++++-- .../GotoTestRelatedProvider.java | 8 +- .../intellij/ui/popup/list/ListPopupImpl.java | 6 +- .../testAssistant/TestDataRelatedItem.java | 17 +++- .../testng/TestNGRelatedFilesProvider.java | 4 +- .../binding/FormRelatedFilesProvider.java | 6 +- .../navigation/DomGotoRelatedItem.java | 5 +- .../NavigationGutterIconBuilder.java | 4 +- .../navigation/HtmlGotoRelatedProvider.java | 18 +++-- .../LinkedToHtmlFilesContributor.java | 7 +- .../RelatedToHtmlFilesContributor.java | 3 +- 12 files changed, 149 insertions(+), 37 deletions(-) diff --git a/platform/lang-api/src/com/intellij/navigation/GotoRelatedItem.java b/platform/lang-api/src/com/intellij/navigation/GotoRelatedItem.java index 0ecc556d0d3e..4dbbd81a4146 100644 --- a/platform/lang-api/src/com/intellij/navigation/GotoRelatedItem.java +++ b/platform/lang-api/src/com/intellij/navigation/GotoRelatedItem.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. @@ -27,22 +27,26 @@ import java.util.List; /** * @author Dmitry Avdeev + * @author Konstantin Bulenkov */ public class GotoRelatedItem { + private final String myGroup; private final int myMnemonic; private final PsiElement myElement; + public static final String DEFAULT_GROUP_NAME = ""; - protected GotoRelatedItem(@Nullable PsiElement element, final int mnemonic) { + protected GotoRelatedItem(@Nullable PsiElement element, String group, final int mnemonic) { myElement = element; + myGroup = group; myMnemonic = mnemonic; } - public GotoRelatedItem(@NotNull PsiElement element) { - this(element, -1); + public GotoRelatedItem(@NotNull PsiElement element, String group) { + this(element, group, -1); } - protected GotoRelatedItem() { - this(null, -1); + public GotoRelatedItem(@NotNull PsiElement element) { + this(element, DEFAULT_GROUP_NAME); } public void navigate() { @@ -67,11 +71,14 @@ public class GotoRelatedItem { public int getMnemonic() { return myMnemonic; } - public static List createItems(@NotNull Collection elements) { + return createItems(elements, DEFAULT_GROUP_NAME); + } + + public static List createItems(@NotNull Collection elements, String group) { List items = new ArrayList(elements.size()); for (PsiElement element : elements) { - items.add(new GotoRelatedItem(element)); + items.add(new GotoRelatedItem(element, group)); } return items; } @@ -88,6 +95,10 @@ public class GotoRelatedItem { return true; } + public String getGroup() { + return myGroup; + } + @Override public int hashCode() { return myElement != null ? myElement.hashCode() : 0; diff --git a/platform/lang-impl/src/com/intellij/ide/actions/GotoRelatedFileAction.java b/platform/lang-impl/src/com/intellij/ide/actions/GotoRelatedFileAction.java index 79c9b76eb08e..c6e22e785a23 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/GotoRelatedFileAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/GotoRelatedFileAction.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. @@ -26,11 +26,14 @@ import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.PopupStep; import com.intellij.openapi.ui.popup.util.BaseListPopupStep; import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.ui.ColoredListCellRenderer; +import com.intellij.ui.SeparatorWithText; import com.intellij.ui.SimpleTextAttributes; import com.intellij.ui.popup.list.ListPopupImpl; +import com.intellij.ui.popup.list.PopupListElementRenderer; import com.intellij.util.Processor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -97,6 +100,8 @@ public class GotoRelatedFileAction extends AnAction { final String title, final Processor processor) { final Ref hasMnemonic = Ref.create(false); + final Ref rendererRef = Ref.create(null); + final DefaultPsiElementCellRenderer renderer = new DefaultPsiElementCellRenderer() { { setFocusBorderEnabled(false); @@ -190,12 +195,55 @@ public class GotoRelatedFileAction extends AnAction { return super.onChosen(selectedValue, finalChoice); } }) { - @Override - protected ListCellRenderer getListElementRenderer() { - return renderer; - } }; + popup.getList().setCellRenderer(new PopupListElementRenderer(popup) { + Map separators = new HashMap(); + { + final ListModel model = popup.getList().getModel(); + String current = null; + boolean hasTitle = false; + for (int i = 0; i < model.getSize(); i++) { + final Object element = model.getElementAt(i); + final GotoRelatedItem item = itemsMap.get(element); + if (!StringUtil.equals(current, item.getGroup())) { + current = item.getGroup(); + separators.put(element, current); + if (!hasTitle && !StringUtil.isEmpty(current)) { + hasTitle = true; + } + } + } + + if (!hasTitle) { + separators.remove(model.getElementAt(0)); + } + } + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + final Component component = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + final String separator = separators.get(value); + + if (separator != null) { + JPanel panel = new JPanel(new BorderLayout()); + panel.add(component, BorderLayout.CENTER); + final SeparatorWithText sep = new SeparatorWithText() { + @Override + protected void paintComponent(Graphics g) { + g.setColor(Color.WHITE); + g.fillRect(0,0,getWidth(), getHeight()); + super.paintComponent(g); + } + }; + sep.setCaption(separator); + panel.add(sep, BorderLayout.NORTH); + return panel; + } + return component; + } + }); + popup.setMinimumSize(new Dimension(200, -1)); + for (Object item : elements) { final int mnemonic = getMnemonic(item, itemsMap); if (mnemonic != -1) { @@ -226,9 +274,32 @@ public class GotoRelatedFileAction extends AnAction { items.addAll(provider.getItems(dataContext)); } } + sortByGroupNames(items); return new ArrayList(items); } + private static void sortByGroupNames(Set items) { + Map> map = new HashMap>(); + for (GotoRelatedItem item : items) { + final String key = item.getGroup(); + if (!map.containsKey(key)) { + map.put(key, new ArrayList()); + } + map.get(key).add(item); + } + final List keys = new ArrayList(map.keySet()); + Collections.sort(keys, new Comparator() { + @Override + public int compare(String o1, String o2) { + return StringUtil.isEmpty(o1) ? 1 : StringUtil.isEmpty(o2) ? -1 : o1.compareTo(o2); + } + }); + items.clear(); + for (String key : keys) { + items.addAll(map.get(key)); + } + } + @Override public void update(AnActionEvent e) { e.getPresentation().setEnabled(LangDataKeys.PSI_FILE.getData(e.getDataContext()) != null); diff --git a/platform/lang-impl/src/com/intellij/testIntegration/GotoTestRelatedProvider.java b/platform/lang-impl/src/com/intellij/testIntegration/GotoTestRelatedProvider.java index 531eee55d427..d618fa60d0ab 100644 --- a/platform/lang-impl/src/com/intellij/testIntegration/GotoTestRelatedProvider.java +++ b/platform/lang-impl/src/com/intellij/testIntegration/GotoTestRelatedProvider.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. @@ -35,15 +35,17 @@ public class GotoTestRelatedProvider extends GotoRelatedProvider { public List getItems(@NotNull DataContext context) { final PsiFile file = LangDataKeys.PSI_FILE.getData(context); List result; - if (TestFinderHelper.isTest(file)) { + final boolean isTest = TestFinderHelper.isTest(file); + if (isTest) { result = TestFinderHelper.findClassesForTest(file); } else { result = TestFinderHelper.findTestsForClass(file); } + if (!result.isEmpty()) { final List items = new ArrayList(); for (PsiElement element : result) { - items.add(new GotoRelatedItem(element)); + items.add(new GotoRelatedItem(element, isTest ? "Tests" : "Testee classes")); } return items; } diff --git a/platform/platform-impl/src/com/intellij/ui/popup/list/ListPopupImpl.java b/platform/platform-impl/src/com/intellij/ui/popup/list/ListPopupImpl.java index 64213cc37d00..ba29b515cea4 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/list/ListPopupImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/list/ListPopupImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 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. @@ -183,6 +183,10 @@ public class ListPopupImpl extends WizardPopup implements ListPopup { return count; } + public JList getList() { + return myList; + } + protected JComponent createContent() { myMouseMotionListener = new MyMouseMotionListener(); myMouseListener = new MyMouseListener(); diff --git a/plugins/IdeaTestAssistant/src/com/intellij/testAssistant/TestDataRelatedItem.java b/plugins/IdeaTestAssistant/src/com/intellij/testAssistant/TestDataRelatedItem.java index 871808e97279..900279214ac5 100644 --- a/plugins/IdeaTestAssistant/src/com/intellij/testAssistant/TestDataRelatedItem.java +++ b/plugins/IdeaTestAssistant/src/com/intellij/testAssistant/TestDataRelatedItem.java @@ -1,3 +1,18 @@ +/* + * 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.testAssistant; import com.intellij.navigation.GotoRelatedItem; @@ -22,7 +37,7 @@ public class TestDataRelatedItem extends GotoRelatedItem{ private final PsiMethod myMethod; public TestDataRelatedItem(@NotNull PsiMethod method, @NotNull Editor editor, @NotNull Collection testDataFiles) { - super(method); + super(method, "Test Data"); myMethod = method; myEditor = editor; myTestDataFiles.addAll(testDataFiles); diff --git a/plugins/testng/src/com/theoryinpractice/testng/TestNGRelatedFilesProvider.java b/plugins/testng/src/com/theoryinpractice/testng/TestNGRelatedFilesProvider.java index abb1fa704081..927a81dbc402 100644 --- a/plugins/testng/src/com/theoryinpractice/testng/TestNGRelatedFilesProvider.java +++ b/plugins/testng/src/com/theoryinpractice/testng/TestNGRelatedFilesProvider.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. @@ -84,7 +84,7 @@ public class TestNGRelatedFilesProvider extends GotoRelatedProvider { } if (!tags.isEmpty()) { - return GotoRelatedItem.createItems(tags); + return GotoRelatedItem.createItems(tags, "TestNG"); } } psiClass = PsiTreeUtil.getParentOfType(psiClass, PsiClass.class); diff --git a/plugins/ui-designer/src/com/intellij/uiDesigner/binding/FormRelatedFilesProvider.java b/plugins/ui-designer/src/com/intellij/uiDesigner/binding/FormRelatedFilesProvider.java index d4c5be1f7407..7918ace54c1d 100644 --- a/plugins/ui-designer/src/com/intellij/uiDesigner/binding/FormRelatedFilesProvider.java +++ b/plugins/ui-designer/src/com/intellij/uiDesigner/binding/FormRelatedFilesProvider.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. @@ -44,7 +44,7 @@ public class FormRelatedFilesProvider extends GotoRelatedProvider { while (psiClass != null) { List forms = FormClassIndex.findFormsBoundToClass(psiClass); if (!forms.isEmpty()) { - return GotoRelatedItem.createItems(forms); + return GotoRelatedItem.createItems(forms, "UI Forms"); } psiClass = PsiTreeUtil.getParentOfType(psiClass, PsiClass.class); } @@ -58,7 +58,7 @@ public class FormRelatedFilesProvider extends GotoRelatedProvider { Project project = file.getProject(); PsiClass aClass = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project)); if (aClass != null) { - return Collections.singletonList(new GotoRelatedItem(aClass)); + return Collections.singletonList(new GotoRelatedItem(aClass, "Java")); } } } diff --git a/xml/dom-openapi/src/com/intellij/codeInsight/navigation/DomGotoRelatedItem.java b/xml/dom-openapi/src/com/intellij/codeInsight/navigation/DomGotoRelatedItem.java index 9b699716ab0b..65df241472c4 100644 --- a/xml/dom-openapi/src/com/intellij/codeInsight/navigation/DomGotoRelatedItem.java +++ b/xml/dom-openapi/src/com/intellij/codeInsight/navigation/DomGotoRelatedItem.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. @@ -17,7 +17,6 @@ package com.intellij.codeInsight.navigation; import com.intellij.navigation.GotoRelatedItem; import com.intellij.util.xml.DomElement; -import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -28,7 +27,7 @@ public class DomGotoRelatedItem extends GotoRelatedItem { private final DomElement myElement; public DomGotoRelatedItem(DomElement element) { - super(element.getXmlElement()); + super(element.getXmlElement(), "XML"); myElement = element; } diff --git a/xml/dom-openapi/src/com/intellij/codeInsight/navigation/NavigationGutterIconBuilder.java b/xml/dom-openapi/src/com/intellij/codeInsight/navigation/NavigationGutterIconBuilder.java index 60efe1dec69c..23324548e9f6 100644 --- a/xml/dom-openapi/src/com/intellij/codeInsight/navigation/NavigationGutterIconBuilder.java +++ b/xml/dom-openapi/src/com/intellij/codeInsight/navigation/NavigationGutterIconBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 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. @@ -93,7 +93,7 @@ public class NavigationGutterIconBuilder { @NotNull @Override public Collection fun(PsiElement dom) { - return Collections.singletonList(new GotoRelatedItem(dom)); + return Collections.singletonList(new GotoRelatedItem(dom, "XML")); } }; diff --git a/xml/impl/src/com/intellij/navigation/HtmlGotoRelatedProvider.java b/xml/impl/src/com/intellij/navigation/HtmlGotoRelatedProvider.java index 5f4d850a5d94..7cb752134e56 100644 --- a/xml/impl/src/com/intellij/navigation/HtmlGotoRelatedProvider.java +++ b/xml/impl/src/com/intellij/navigation/HtmlGotoRelatedProvider.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. @@ -24,9 +24,9 @@ import com.intellij.psi.xml.XmlFile; import com.intellij.util.containers.HashSet; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Set; /** * Created by IntelliJ IDEA. @@ -44,10 +44,7 @@ public class HtmlGotoRelatedProvider extends GotoRelatedProvider { return Collections.emptyList(); } - HashSet resultSet = new HashSet(); - fillRelatedFiles(file, resultSet); - - return GotoRelatedItem.createItems(resultSet); + return getRelatedFiles(file); } private static boolean isAvailable(@NotNull PsiFile psiFile) { @@ -60,15 +57,22 @@ public class HtmlGotoRelatedProvider extends GotoRelatedProvider { return false; } - private static void fillRelatedFiles(@NotNull PsiFile file, @NotNull Set resultSet) { + private static List getRelatedFiles(@NotNull PsiFile file) { + List items = new ArrayList(); + for (PsiFile psiFile : file.getViewProvider().getAllFiles()) { if (psiFile instanceof XmlFile) { final XmlFile xmlFile = (XmlFile)psiFile; for (RelatedToHtmlFilesContributor contributor : RelatedToHtmlFilesContributor.EP_NAME.getExtensions()) { + HashSet resultSet = new HashSet(); contributor.fillRelatedFiles(xmlFile, resultSet); + for (PsiFile f: resultSet) { + items.add(new GotoRelatedItem(f, contributor.getGroupName())); + } } } } + return items; } } diff --git a/xml/impl/src/com/intellij/navigation/LinkedToHtmlFilesContributor.java b/xml/impl/src/com/intellij/navigation/LinkedToHtmlFilesContributor.java index 276171b544a5..86f83e98b48f 100644 --- a/xml/impl/src/com/intellij/navigation/LinkedToHtmlFilesContributor.java +++ b/xml/impl/src/com/intellij/navigation/LinkedToHtmlFilesContributor.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. @@ -67,4 +67,9 @@ public class LinkedToHtmlFilesContributor extends RelatedToHtmlFilesContributor } }); } + + @Override + public String getGroupName() { + return "Linked files"; + } } diff --git a/xml/impl/src/com/intellij/navigation/RelatedToHtmlFilesContributor.java b/xml/impl/src/com/intellij/navigation/RelatedToHtmlFilesContributor.java index dc27a3a7e019..5aef333823e7 100644 --- a/xml/impl/src/com/intellij/navigation/RelatedToHtmlFilesContributor.java +++ b/xml/impl/src/com/intellij/navigation/RelatedToHtmlFilesContributor.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. @@ -30,4 +30,5 @@ public abstract class RelatedToHtmlFilesContributor { ExtensionPointName.create("com.intellij.xml.relatedToHtmlFilesContributor"); public abstract void fillRelatedFiles(@NotNull XmlFile xmlFile, @NotNull Set resultSet); + public abstract String getGroupName(); } From 1c40e5d796690f40e3d0363fa3788ee26bf847a9 Mon Sep 17 00:00:00 2001 From: anna Date: Wed, 9 May 2012 18:29:36 +0200 Subject: [PATCH 02/31] framework configuration: allow to skip library addition --- .../framework/library/FrameworkSupportWithLibrary.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/idea-ui/src/com/intellij/framework/library/FrameworkSupportWithLibrary.java b/java/idea-ui/src/com/intellij/framework/library/FrameworkSupportWithLibrary.java index dce7da5e3f85..9ca15ad45ee5 100644 --- a/java/idea-ui/src/com/intellij/framework/library/FrameworkSupportWithLibrary.java +++ b/java/idea-ui/src/com/intellij/framework/library/FrameworkSupportWithLibrary.java @@ -16,14 +16,14 @@ package com.intellij.framework.library; import com.intellij.openapi.roots.ui.configuration.libraries.CustomLibraryDescription; -import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * @author nik */ public interface FrameworkSupportWithLibrary { - @NotNull + @Nullable CustomLibraryDescription createLibraryDescription(); boolean isLibraryOnly(); From 2b4248f6ceaae8aba9ffbfc4ebd0e3e78edfbf22 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Wed, 9 May 2012 14:27:45 +0400 Subject: [PATCH 03/31] Correct @SkipInHeadlessEnvironment processing for junit4-style tests --- .../src/com/intellij/TestCaseLoader.java | 5 ++++- .../intellij/testFramework/PlatformTestUtil.java | 14 +++++++++++++- .../com/intellij/testFramework/UsefulTestCase.java | 14 ++------------ 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/platform/testFramework/src/com/intellij/TestCaseLoader.java b/platform/testFramework/src/com/intellij/TestCaseLoader.java index cc5aed395f84..a64d838c5643 100644 --- a/platform/testFramework/src/com/intellij/TestCaseLoader.java +++ b/platform/testFramework/src/com/intellij/TestCaseLoader.java @@ -36,6 +36,7 @@ import java.io.*; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; +import java.util.List; @SuppressWarnings({"HardCodedStringLiteral", "UseOfSystemOutOrSystemErr", "CallToPrintStackTrace", "TestOnlyProblems"}) public class TestCaseLoader { @@ -98,7 +99,9 @@ public class TestCaseLoader { * shouldLoadTestCase () to determine that. */ void addClassIfTestCase(final Class testCaseClass) { - if (shouldAddTestCase(testCaseClass, true) && testCaseClass != myFirstTestClass && testCaseClass != myLastTestClass) { + if (shouldAddTestCase(testCaseClass, true) && testCaseClass != myFirstTestClass && testCaseClass != myLastTestClass + && PlatformTestUtil.canRunTest(testCaseClass)) + { myClassList.add(testCaseClass); } } diff --git a/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java b/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java index 0ab4905a7ca2..95f715bd7854 100644 --- a/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java +++ b/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.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. @@ -448,6 +448,18 @@ public class PlatformTestUtil { return total/(n / part); } + public static boolean canRunTest(@NotNull Class testCaseClass) { + if (GraphicsEnvironment.isHeadless()) { + for (Class clazz = testCaseClass; clazz != null; clazz = clazz.getSuperclass()) { + if (clazz.getAnnotation(SkipInHeadlessEnvironment.class) != null) { + System.out.println("Class '" + testCaseClass.getName() + "' is skipped because it requires working UI environment"); + return false; + } + } + } + return true; + } + public static class TestInfo { private final ThrowableRunnable test; // runnable to measure private final int expected; // millis the test is expected to run diff --git a/platform/testFramework/src/com/intellij/testFramework/UsefulTestCase.java b/platform/testFramework/src/com/intellij/testFramework/UsefulTestCase.java index 98067ba583dc..576b80af610b 100644 --- a/platform/testFramework/src/com/intellij/testFramework/UsefulTestCase.java +++ b/platform/testFramework/src/com/intellij/testFramework/UsefulTestCase.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. @@ -258,17 +258,7 @@ public abstract class UsefulTestCase extends TestCase { } protected boolean shouldRunTest() { - if (isInHeadlessEnvironment()) { - Class aClass = getClass(); - while (aClass != null) { - if (aClass.getAnnotation(SkipInHeadlessEnvironment.class) != null) { - System.out.println("Test '" + getClass().getName() + "." + getName() + "' is skipped because it requires working UI environment"); - return false; - } - aClass = aClass.getSuperclass(); - } - } - return true; + return PlatformTestUtil.canRunTest(getClass()); } public static void edt(Runnable r) { From 2bcb01f172268ed26c9f7261dded3c502b1093c3 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Wed, 9 May 2012 18:41:57 +0400 Subject: [PATCH 04/31] IDEA-85813 Field/variable declaration/assignment alignment lost with Refactor>Rename or Reformat Code Expand field range to the field group range during the formatting if necessary --- .../FieldInColumnsPreFormatProcessor.java | 120 ++++++++++++++++++ .../codeStyle/FormatCommentsProcessor.java | 6 +- .../renameField/afterFieldInColumns.java | 23 ++++ .../renameField/beforeFieldInColumns.java | 23 ++++ .../intellij/refactoring/RenameFieldTest.java | 25 ++++ .../source/codeStyle/PreFormatProcessor.java | 6 +- resources/src/META-INF/IdeaPlugin.xml | 1 + 7 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 java/java-impl/src/com/intellij/psi/impl/source/codeStyle/FieldInColumnsPreFormatProcessor.java create mode 100644 java/java-tests/testData/refactoring/renameField/afterFieldInColumns.java create mode 100644 java/java-tests/testData/refactoring/renameField/beforeFieldInColumns.java diff --git a/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/FieldInColumnsPreFormatProcessor.java b/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/FieldInColumnsPreFormatProcessor.java new file mode 100644 index 000000000000..72b27ee31286 --- /dev/null +++ b/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/FieldInColumnsPreFormatProcessor.java @@ -0,0 +1,120 @@ +/* + * 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.impl.source.codeStyle; + +import com.intellij.lang.ASTNode; +import com.intellij.lang.java.JavaLanguage; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiField; +import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.codeStyle.CommonCodeStyleSettings; +import com.intellij.psi.impl.source.tree.ElementType; +import com.intellij.psi.impl.source.tree.JavaJspElementType; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; + +/** + * There is a possible case that the project is configured to keep fields in columns: + *
+ *   class Test {
+ *     int i                 = 1;
+ *     int fieldWithLongName = 2;
+ *   }
+ * 
+ * Suppose that one of the fields is renamed. We want to reformat the whole fields group then in order to keep that 'field columns'. + *

+ * Current extension checks if given range intersects with a field from a field group and expands its boundaries to contain + * the whole group. + *

+ * Thread-safe. + * + * @author Denis Zhdanov + * @since 5/9/12 4:54 PM + */ +public class FieldInColumnsPreFormatProcessor implements PreFormatProcessor { + + @NotNull + @Override + public TextRange process(@NotNull ASTNode element, @NotNull TextRange range) { + //region Checking that everything is ready to expand the range for the 'fields in columns'. + final PsiElement psi = element.getPsi(); + if (psi == null) { + return range; + } + + final PsiFile file = psi.getContainingFile(); + if (file == null) { + return range; + } + + final Project project = psi.getProject(); + final CommonCodeStyleSettings settings + = CodeStyleSettingsManager.getInstance(project).getCurrentSettings().getCommonSettings(JavaLanguage.INSTANCE); + if (!settings.ALIGN_GROUP_FIELD_DECLARATIONS) { + return range; + } + + final PsiElement startElement = file.findElementAt(range.getStartOffset()); + if (startElement == null) { + return range; + } + + final PsiField parent = PsiTreeUtil.getParentOfType(startElement, PsiField.class); + if (parent == null) { + return range; + } + //endregion + + //region Calculating start offset to use by the start offset of the first sibling white space or field to the left of the current field. + int startToUse = range.getStartOffset(); + for (PsiElement f = parent; f != null; f = f.getPrevSibling()) { + final ASTNode node = f.getNode(); + if (node == null) { + break; + } + if (JavaJspElementType.WHITE_SPACE_BIT_SET.contains(node.getElementType()) || f instanceof PsiField) { + startToUse = f.getTextRange().getStartOffset(); + } + else if (!ElementType.JAVA_COMMENT_BIT_SET.contains(node.getElementType())) { + break; + } + } + //endregion + + //region Calculating end offset to use by the end offset of the last field in a group located to the right of the current field. + int endToUse = range.getEndOffset(); + for (PsiElement f = parent; f != null; f = f.getPrevSibling()) { + final ASTNode node = f.getNode(); + if (node == null) { + break; + } + if (f instanceof PsiField) { + endToUse = f.getTextRange().getEndOffset(); + } + else if (!JavaJspElementType.WHITE_SPACE_BIT_SET.contains(node.getElementType()) && + !ElementType.JAVA_COMMENT_BIT_SET.contains(node.getElementType())) + { + break; + } + } + //endregion + + return TextRange.from(startToUse, endToUse); + } +} diff --git a/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/FormatCommentsProcessor.java b/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/FormatCommentsProcessor.java index 4efb46a4b4bd..5c80f43240d7 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/FormatCommentsProcessor.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/FormatCommentsProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 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. @@ -25,10 +25,12 @@ import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.impl.source.SourceTreeToPsiMap; import com.intellij.psi.impl.source.codeStyle.javadoc.CommentFormatter; import com.intellij.psi.javadoc.PsiDocComment; +import org.jetbrains.annotations.NotNull; public class FormatCommentsProcessor implements PreFormatProcessor { + @NotNull @Override - public TextRange process(final ASTNode element, final TextRange range) { + public TextRange process(@NotNull final ASTNode element, @NotNull final TextRange range) { final Project project = SourceTreeToPsiMap.treeElementToPsi(element).getProject(); if (!CodeStyleSettingsManager.getSettings(project).ENABLE_JAVADOC_FORMATTING || element.getPsi().getContainingFile().getLanguage() != StdLanguages.JAVA) { diff --git a/java/java-tests/testData/refactoring/renameField/afterFieldInColumns.java b/java/java-tests/testData/refactoring/renameField/afterFieldInColumns.java new file mode 100644 index 000000000000..b2f73979881e --- /dev/null +++ b/java/java-tests/testData/refactoring/renameField/afterFieldInColumns.java @@ -0,0 +1,23 @@ +/* + * 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. + */ +class Test { + int jj = 1; + int fieldWithLongName = 2; + + void test() { + jj = 3; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/renameField/beforeFieldInColumns.java b/java/java-tests/testData/refactoring/renameField/beforeFieldInColumns.java new file mode 100644 index 000000000000..9e214201346c --- /dev/null +++ b/java/java-tests/testData/refactoring/renameField/beforeFieldInColumns.java @@ -0,0 +1,23 @@ +/* + * 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. + */ +class Test { + int i = 1; + int fieldWithLongName = 2; + + void test() { + i = 3; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/refactoring/RenameFieldTest.java b/java/java-tests/testSrc/com/intellij/refactoring/RenameFieldTest.java index d5d7f02d317e..656156bfde98 100644 --- a/java/java-tests/testSrc/com/intellij/refactoring/RenameFieldTest.java +++ b/java/java-tests/testSrc/com/intellij/refactoring/RenameFieldTest.java @@ -1,3 +1,19 @@ +/* + * 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. + */ + /* * Created by IntelliJ IDEA. * User: dsl @@ -10,7 +26,9 @@ package com.intellij.refactoring; import com.intellij.JavaTestUtil; import com.intellij.codeInsight.TargetElementUtilBase; +import com.intellij.lang.java.JavaLanguage; import com.intellij.psi.PsiElement; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.refactoring.rename.RenameProcessor; import com.intellij.refactoring.rename.RenameWrongRefHandler; import org.jetbrains.annotations.NonNls; @@ -66,6 +84,13 @@ public class RenameFieldTest extends LightRefactoringTestCase { assertFalse(RenameWrongRefHandler.isAvailable(getProject(), getEditor(), getFile())); } + public void testFieldInColumns() throws Exception { + // Assuming that test infrastructure setups temp settings (CodeStyleSettingsManager.setTemporarySettings()) and we don't + // need to perform explicit clean-up at the test level. + CodeStyleSettingsManager.getSettings(getProject()).getCommonSettings(JavaLanguage.INSTANCE).ALIGN_GROUP_FIELD_DECLARATIONS = true; + doTest("jj", "java"); + } + protected static void perform(String newName) { PsiElement element = TargetElementUtilBase.findTargetElement(myEditor, TargetElementUtilBase .ELEMENT_NAME_ACCEPTED | TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED); diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/PreFormatProcessor.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/PreFormatProcessor.java index c4f243e4300d..f746b33cac31 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/PreFormatProcessor.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/PreFormatProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 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,7 @@ package com.intellij.psi.impl.source.codeStyle; import com.intellij.lang.ASTNode; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.util.TextRange; +import org.jetbrains.annotations.NotNull; /** * @author yole @@ -26,5 +27,6 @@ import com.intellij.openapi.util.TextRange; public interface PreFormatProcessor { ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.preFormatProcessor"); - TextRange process(ASTNode element, TextRange range); + @NotNull + TextRange process(@NotNull ASTNode element, @NotNull TextRange range); } diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index de479ae10723..cb3175edc39c 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -888,6 +888,7 @@ + From a458b2d49e7ae55935084cf0e03a2530795ae00c Mon Sep 17 00:00:00 2001 From: "Maxim.Medvedev" Date: Sun, 6 May 2012 14:24:36 +0400 Subject: [PATCH 05/31] don't highlight method refs with at least one resolve result --- .../jetbrains/plugins/groovy/annotator/GroovyAnnotator.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java index 98358c2d2891..ca1e094f3b91 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/annotator/GroovyAnnotator.java @@ -265,6 +265,12 @@ public class GroovyAnnotator extends GroovyElementVisitor implements Annotator { GrExpression qualifier = referenceExpression.getQualifierExpression(); if (qualifier == null && isDeclarationAssignment(referenceExpression)) return; + if (qualifier != null && referenceExpression.getDotTokenType() == GroovyTokenTypes.mMEMBER_POINTER) { + if (results.length > 0) { + return; + } + } + // If it is reference to map.key we shouldn't highlight key unresolved if (!(parent instanceof GrCall) && ResolveUtil.isKeyOfMap(referenceExpression)) { PsiElement refNameElement = referenceExpression.getReferenceNameElement(); From b172063f8f33776febf2d59ee3e93dba886a7dcd Mon Sep 17 00:00:00 2001 From: "Maxim.Medvedev" Date: Sun, 6 May 2012 15:40:37 +0400 Subject: [PATCH 06/31] JavaCreatePropertyFix replaces key literal by newly created one --- .../i18n/JavaCreatePropertyFix.java | 51 +++++++++++++++---- .../references/CreatePropertyFix.java | 13 +++-- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaCreatePropertyFix.java b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaCreatePropertyFix.java index d32b9507e744..ff765b3736bc 100644 --- a/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaCreatePropertyFix.java +++ b/plugins/java-i18n/src/com/intellij/codeInspection/i18n/JavaCreatePropertyFix.java @@ -18,11 +18,15 @@ package com.intellij.codeInspection.i18n; import com.intellij.lang.properties.psi.PropertiesFile; import com.intellij.lang.properties.references.CreatePropertyFix; import com.intellij.lang.properties.references.I18nizeQuickFixDialog; +import com.intellij.lang.properties.references.I18nizeQuickFixModel; +import com.intellij.openapi.application.AccessToken; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiLiteralExpression; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -32,19 +36,47 @@ import java.util.List; * @author Maxim.Mossienko */ public class JavaCreatePropertyFix extends CreatePropertyFix { + private static final Logger LOG = Logger.getInstance(JavaCreatePropertyFix.class); + public JavaCreatePropertyFix() {} public JavaCreatePropertyFix(PsiElement element, String key, final List propertiesFiles) { super(element, key, propertiesFiles); } + @Override + protected Pair doAction(Project project, PsiElement psiElement, I18nizeQuickFixModel model) { + final Pair result = super.doAction(project, psiElement, model); + if (result != null && psiElement instanceof PsiLiteralExpression) { + final String key = result.first; + + final StringBuilder buffer = new StringBuilder(); + buffer.append('"'); + StringUtil.escapeStringCharacters(key.length(), key, buffer); + buffer.append('"'); + + final AccessToken token = ApplicationManager.getApplication().acquireWriteActionLock(JavaCreatePropertyFix.class); + try { + final PsiExpression newKeyLiteral = JavaPsiFacade.getElementFactory(project).createExpressionFromText(buffer.toString(), null); + psiElement.replace(newKeyLiteral); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + finally { + token.finish(); + } + } + return result; + } + @Nullable - protected static Pair invokeAction(@NotNull final Project project, - @NotNull PsiFile file, - @NotNull PsiElement psiElement, - @Nullable final String suggestedKey, - @Nullable String suggestedValue, - @Nullable final List propertiesFiles) { + protected Pair invokeAction(@NotNull final Project project, + @NotNull PsiFile file, + @NotNull PsiElement psiElement, + @Nullable final String suggestedKey, + @Nullable String suggestedValue, + @Nullable final List propertiesFiles) { final PsiLiteralExpression literalExpression = psiElement instanceof PsiLiteralExpression ? (PsiLiteralExpression)psiElement : null; final String propertyValue = suggestedValue == null ? "" : suggestedValue; @@ -59,5 +91,4 @@ public class JavaCreatePropertyFix extends CreatePropertyFix { ); return doAction(project, psiElement, dialog); } - } diff --git a/plugins/properties/src/com/intellij/lang/properties/references/CreatePropertyFix.java b/plugins/properties/src/com/intellij/lang/properties/references/CreatePropertyFix.java index b875f857999b..0a3fc8ef7b0d 100644 --- a/plugins/properties/src/com/intellij/lang/properties/references/CreatePropertyFix.java +++ b/plugins/properties/src/com/intellij/lang/properties/references/CreatePropertyFix.java @@ -88,11 +88,11 @@ public class CreatePropertyFix implements IntentionAction, LocalQuickFix { } @Nullable - private static Pair invokeAction(@NotNull final Project project, - @NotNull PsiFile file, - @NotNull PsiElement psiElement, - @Nullable final String suggestedKey, - @Nullable final List propertiesFiles) { + private Pair invokeAction(@NotNull final Project project, + @NotNull PsiFile file, + @NotNull PsiElement psiElement, + @Nullable final String suggestedKey, + @Nullable final List propertiesFiles) { final I18nizeQuickFixModel model; final I18nizeQuickFixDialog.DialogCustomization dialogCustomization = createDefaultCustomization(suggestedKey, propertiesFiles); @@ -128,8 +128,7 @@ public class CreatePropertyFix implements IntentionAction, LocalQuickFix { return new I18nizeQuickFixDialog.DialogCustomization(NAME, false, true, propertiesFiles, suggestedKey == null ? "" : suggestedKey); } - protected static Pair doAction(Project project, PsiElement psiElement, - I18nizeQuickFixModel model) { + protected Pair doAction(Project project, PsiElement psiElement, I18nizeQuickFixModel model) { if (!model.hasValidData()) { return null; } From 650fb6da9e4fc3b8da4651808a2c01940f0f23c0 Mon Sep 17 00:00:00 2001 From: "Maxim.Medvedev" Date: Sun, 6 May 2012 17:23:45 +0400 Subject: [PATCH 07/31] highlight incorrect throw statements --- .../GroovyAssignabilityCheckInspection.java | 11 +++++++ .../groovy/lang/GroovyHighlightingTest.groovy | 32 +++++++++++++------ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/assignment/GroovyAssignabilityCheckInspection.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/assignment/GroovyAssignabilityCheckInspection.java index d12ed7428ad5..6551ffc30a0c 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/assignment/GroovyAssignabilityCheckInspection.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/codeInspection/assignment/GroovyAssignabilityCheckInspection.java @@ -46,6 +46,7 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArg import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrReturnStatement; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrThrowStatement; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrString; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrIndexProperty; @@ -349,6 +350,16 @@ public class GroovyAssignabilityCheckInspection extends BaseInspection { } } + @Override + public void visitThrowStatement(GrThrowStatement throwStatement) { + super.visitThrowStatement(throwStatement); + + final GrExpression exception = throwStatement.getException(); + if (exception != null) { + checkAssignability(PsiType.getJavaLangThrowable(throwStatement.getManager(), throwStatement.getResolveScope()), exception, exception); + } + } + private boolean checkLiteralConstructorApplicability(GroovyResolveResult result, GrListOrMap listOrMap, boolean checkUnknownArgs) { final PsiElement element = result.getElement(); LOG.assertTrue(element instanceof PsiMethod && ((PsiMethod)element).isConstructor()); diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyHighlightingTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyHighlightingTest.groovy index 40b5b5007a4b..2e24f7a14738 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyHighlightingTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/GroovyHighlightingTest.groovy @@ -867,7 +867,7 @@ C< } public void testRawClosureReturnType() { - myFixture.configureByText('_.groovy', '''\ + testHighlighting('''\ class A { A(T t) {this.t = t} @@ -880,17 +880,17 @@ class A { def a = new A(new Date()) Date d = a.cl() -''') - testHighlighting(GroovyUncheckedAssignmentOfMemberOfRawTypeInspection) +''', GroovyUncheckedAssignmentOfMemberOfRawTypeInspection) } - private void testHighlighting(Class... inspections) { + private void testHighlighting(String text, Class... inspections) { + myFixture.configureByText('_.groovy', text) myFixture.enableInspections(inspections) myFixture.testHighlighting(true, false, true) } void testMethodRefs1() { - myFixture.configureByText('_.groovy', '''\ + testHighlighting('''\ class A { int foo(){2} @@ -903,12 +903,11 @@ int i = foo() int i2 = foo(2) Date d = foo(2) Date d2 = foo() -''') - testHighlighting(GroovyAssignabilityCheckInspection) +''', GroovyAssignabilityCheckInspection) } void testMethodRefs2() { - myFixture.configureByText('_.groovy', '''\ + testHighlighting('''\ class Bar { def foo(int i, String s2) {s2} def foo(int i, int i2) {i2} @@ -920,8 +919,21 @@ String s = cl("2") int s2 = cl("2") int i = cl(3) String i2 = cl(3) -''') - testHighlighting(GroovyAssignabilityCheckInspection) +''', GroovyAssignabilityCheckInspection) } + void testThrowObject() { + testHighlighting('''\ +def foo() { + throw new RuntimeException() +} +def bar () { + throw new Object() +} + +def test() { + throw new Throwable() +} +''', GroovyAssignabilityCheckInspection) + } } \ No newline at end of file From 0c4947bf16928bcb167259d9ad683291de2560ed Mon Sep 17 00:00:00 2001 From: "Maxim.Medvedev" Date: Thu, 10 May 2012 11:06:02 +0400 Subject: [PATCH 08/31] fix uppercase test data --- .../jetbrains/plugins/groovy/lang/formatter/FormatterTest.java | 2 +- .../formatter/{MapInArgumentList2.test => mapInArgList2.test} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename plugins/groovy/testdata/groovy/formatter/{MapInArgumentList2.test => mapInArgList2.test} (100%) diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/formatter/FormatterTest.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/formatter/FormatterTest.java index bcc84b3b09b0..b49a9a36c7dd 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/formatter/FormatterTest.java +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/formatter/FormatterTest.java @@ -236,7 +236,7 @@ public class FormatterTest extends GroovyFormatterTestCase { public void testGeese8() {doGeeseTest();} public void testMapInArgumentList() {doTest();} - public void testMapInArgumentList2() { + public void testMapInArgList2() { myTempSettings.getCustomSettings(GroovyCodeStyleSettings.class).ALIGN_NAMED_ARGS_IN_MAP = true; doTest(); } diff --git a/plugins/groovy/testdata/groovy/formatter/MapInArgumentList2.test b/plugins/groovy/testdata/groovy/formatter/mapInArgList2.test similarity index 100% rename from plugins/groovy/testdata/groovy/formatter/MapInArgumentList2.test rename to plugins/groovy/testdata/groovy/formatter/mapInArgList2.test From 63aa8da36e0c5c228ea6005a49f01513a9be2fc6 Mon Sep 17 00:00:00 2001 From: Dennis Ushakov Date: Sat, 5 May 2012 16:47:31 +0400 Subject: [PATCH 09/31] capitalization --- .../src/messages/ExecutionBundle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/platform-resources-en/src/messages/ExecutionBundle.properties b/platform/platform-resources-en/src/messages/ExecutionBundle.properties index 4321036ede15..c0c60bb80bb0 100644 --- a/platform/platform-resources-en/src/messages/ExecutionBundle.properties +++ b/platform/platform-resources-en/src/messages/ExecutionBundle.properties @@ -287,7 +287,7 @@ logs.tab.title=Logs before.launch.panel.title=Before Launch before.launch.panel.empty=There are no tasks to run before launch before.launch.panel.cyclic_dependency_warning=''{0}'' has already configured to be launched before {1}.\nSuch cyclic dependencies are not allowed. -before.launch.run.another.configuration=Run another Configuration +before.launch.run.another.configuration=Run Another Configuration before.launch.run.certain.configuration=Run ''{0}'' before.launch.run.unknown.task=Unknown task action.name.save.as.configuration=Save As From 78a411e6d1aa9ef35409ecf94775d27ca597d1a9 Mon Sep 17 00:00:00 2001 From: Dennis Ushakov Date: Thu, 10 May 2012 13:22:17 +0400 Subject: [PATCH 10/31] capitalization --- .../src/messages/ApplicationBundle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/platform-resources-en/src/messages/ApplicationBundle.properties b/platform/platform-resources-en/src/messages/ApplicationBundle.properties index dbf3f5ba5a6d..decd52299ae1 100644 --- a/platform/platform-resources-en/src/messages/ApplicationBundle.properties +++ b/platform/platform-resources-en/src/messages/ApplicationBundle.properties @@ -340,7 +340,7 @@ checkbox.collapse.xml.tags=XML tags checkbox.collapse.anonymous.classes=Anonymous classes checkbox.collapse.closures="Closures" (anonymous classes implementing one method) checkbox.collapse.generic.constructor.parameters=Generic constructor and method parameters -checkbox.collapse.i18n.messages=I18n Strings +checkbox.collapse.i18n.messages=I18n strings checkbox.collapse.annotations=Annotations checkbox.collapse.inner.classes=Inner classes checkbox.collapse.simple.property.accessors=Simple property accessors From 8a5da8fa1801913cf9cf00ddd50850af4c824bd4 Mon Sep 17 00:00:00 2001 From: Dennis Ushakov Date: Thu, 10 May 2012 13:36:46 +0400 Subject: [PATCH 11/31] capitalization --- .../com/maddyhome/idea/copyright/ui/TemplateCommentPanel.form | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/copyright/src/com/maddyhome/idea/copyright/ui/TemplateCommentPanel.form b/plugins/copyright/src/com/maddyhome/idea/copyright/ui/TemplateCommentPanel.form index c4fe5a943446..6eaac456e205 100644 --- a/plugins/copyright/src/com/maddyhome/idea/copyright/ui/TemplateCommentPanel.form +++ b/plugins/copyright/src/com/maddyhome/idea/copyright/ui/TemplateCommentPanel.form @@ -180,7 +180,7 @@ - + From 78573fbd727208a50a0c3cb9bb3ddf0436ea526c Mon Sep 17 00:00:00 2001 From: Sergey Evdokimov Date: Thu, 10 May 2012 14:10:22 +0400 Subject: [PATCH 12/31] Revert wrong commit. --- .../maven/utils/MavenProblemFileHighlighter.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenProblemFileHighlighter.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenProblemFileHighlighter.java index 3df60f79db24..20b2b3293c0b 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenProblemFileHighlighter.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenProblemFileHighlighter.java @@ -15,6 +15,8 @@ */ package org.jetbrains.idea.maven.utils; +import com.intellij.openapi.application.AccessToken; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.vfs.VirtualFile; @@ -30,7 +32,13 @@ public class MavenProblemFileHighlighter implements Condition { } public boolean value(final VirtualFile file) { - PsiFile psiFile = PsiManager.getInstance(myProject).findFile(file); - return psiFile != null && MavenDomUtil.isMavenFile(psiFile); + AccessToken accessToken = ApplicationManager.getApplication().acquireReadActionLock(); + try { + PsiFile psiFile = PsiManager.getInstance(myProject).findFile(file); + return psiFile != null && MavenDomUtil.isMavenFile(psiFile); + } + finally { + accessToken.finish(); + } } } From 54f5de0d4c29d873ab51e08a7544b45d9bdc8037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20C=C3=A9bron?= Date: Thu, 10 May 2012 12:19:11 +0200 Subject: [PATCH 13/31] DomGotoRelatedItem: add missing CTORs --- .../codeInsight/navigation/DomGotoRelatedItem.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/xml/dom-openapi/src/com/intellij/codeInsight/navigation/DomGotoRelatedItem.java b/xml/dom-openapi/src/com/intellij/codeInsight/navigation/DomGotoRelatedItem.java index 65df241472c4..f58e0bc4f454 100644 --- a/xml/dom-openapi/src/com/intellij/codeInsight/navigation/DomGotoRelatedItem.java +++ b/xml/dom-openapi/src/com/intellij/codeInsight/navigation/DomGotoRelatedItem.java @@ -27,7 +27,15 @@ public class DomGotoRelatedItem extends GotoRelatedItem { private final DomElement myElement; public DomGotoRelatedItem(DomElement element) { - super(element.getXmlElement(), "XML"); + this(element, "XML"); + } + + public DomGotoRelatedItem(DomElement element, String group) { + this(element, group, -1); + } + + public DomGotoRelatedItem(DomElement element, String group, int mnemonic) { + super(element.getXmlElement(), group, mnemonic); myElement = element; } From 44e4a5354ebf0ad39692e961643def458c6c1e92 Mon Sep 17 00:00:00 2001 From: Sergey Evdokimov Date: Thu, 10 May 2012 14:53:41 +0400 Subject: [PATCH 14/31] IDEA-85710 Grails tool window is empty, IAE at AutoScrollFromSourceHandler.() --- .../mvc/projectView/MvcProjectViewPane.java | 41 ++++++++----------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/mvc/projectView/MvcProjectViewPane.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/mvc/projectView/MvcProjectViewPane.java index 871ec1048af0..747e3399e63d 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/mvc/projectView/MvcProjectViewPane.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/mvc/projectView/MvcProjectViewPane.java @@ -89,19 +89,6 @@ public class MvcProjectViewPane extends AbstractProjectViewPSIPane implements Id myDescriptor = descriptor; myId = descriptor.getToolWindowId(); - myAutoScrollFromSourceHandler = new MyAutoScrollFromSourceHandler(); - myAutoScrollToSourceHandler = new AutoScrollToSourceHandler() { - @Override - protected boolean isAutoScrollMode() { - return myAutoScrollToSource; - } - - @Override - protected void setAutoScrollMode(boolean state) { - myAutoScrollToSource = state; - } - }; - class TreeUpdater implements Runnable, PsiModificationTracker.Listener { private volatile boolean myInQueue; @@ -127,6 +114,23 @@ public class MvcProjectViewPane extends AbstractProjectViewPSIPane implements Id myComponent = createComponent(); DataManager.registerDataProvider(myComponent, this); + myAutoScrollFromSourceHandler = new MyAutoScrollFromSourceHandler(); + myAutoScrollToSourceHandler = new AutoScrollToSourceHandler() { + @Override + protected boolean isAutoScrollMode() { + return myAutoScrollToSource; + } + + @Override + protected void setAutoScrollMode(boolean state) { + myAutoScrollToSource = state; + } + }; + + myAutoScrollFromSourceHandler.install(); + myAutoScrollToSourceHandler.install(getTree()); + myAutoScrollToSourceHandler.onMouseClicked(getTree()); + myCopyPasteDelegator = new CopyPasteDelegator(project, myComponent) { @NotNull @Override @@ -166,15 +170,6 @@ public class MvcProjectViewPane extends AbstractProjectViewPSIPane implements Id toolWindow.setTitleActions(new AnAction[]{new ScrollFromSourceAction(), collapseAction}); } - @Override - public JComponent createComponent() { - JComponent component = super.createComponent(); - myAutoScrollFromSourceHandler.install(); - myAutoScrollToSourceHandler.install(getTree()); - myAutoScrollToSourceHandler.onMouseClicked(getTree()); - return component; - } - public String getTitle() { throw new UnsupportedOperationException(); } @@ -462,7 +457,7 @@ public class MvcProjectViewPane extends AbstractProjectViewPSIPane implements Id private class MyAutoScrollFromSourceHandler extends AutoScrollFromSourceHandler { protected MyAutoScrollFromSourceHandler() { - super(MvcProjectViewPane.this.myProject, getTree(), MvcProjectViewPane.this); + super(MvcProjectViewPane.this.myProject, myComponent, MvcProjectViewPane.this); } @Override From 7d5dec2d9bf00aee7ada5ddc2b5db5ec467a24a1 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Thu, 10 May 2012 15:03:39 +0400 Subject: [PATCH 15/31] Formatter: correct processing for 'indented sub-blocks of aligned block' --- .../formatting/AbstractBlockWrapper.java | 93 +++++++++++-------- 1 file changed, 52 insertions(+), 41 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/formatting/AbstractBlockWrapper.java b/platform/lang-impl/src/com/intellij/formatting/AbstractBlockWrapper.java index e79d7e17c076..77908a96df12 100644 --- a/platform/lang-impl/src/com/intellij/formatting/AbstractBlockWrapper.java +++ b/platform/lang-impl/src/com/intellij/formatting/AbstractBlockWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 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. @@ -236,14 +236,15 @@ public abstract class AbstractBlockWrapper { else { return childIndent.add(myParent.getChildOffset(this, options, targetBlockStartOffset)); } - } else if (!getWhiteSpace().containsLineFeeds()) { - if (isIndentAffectedAlignment(child)) { - return createAlignmentIndent(childIndent, child); - } - else { - return childIndent.add(myParent.getChildOffset(this, options, targetBlockStartOffset)); - } - } else { + } + else if (!getWhiteSpace().containsLineFeeds()) { + final IndentData indent = createAlignmentIndent(childIndent, child); + if (indent != null) { + return indent; + } + return childIndent.add(myParent.getChildOffset(this, options, targetBlockStartOffset)); + } + else { if (myParent == null) return childIndent.add(getWhiteSpace()); if (getIndent().isAbsolute()) { if (myParent.myParent != null) { @@ -254,12 +255,11 @@ public abstract class AbstractBlockWrapper { } } if ((myFlags & CAN_USE_FIRST_CHILD_INDENT_AS_BLOCK_INDENT) != 0) { - if (isIndentAffectedAlignment(child)) { - return createAlignmentIndent(childIndent, child); - } - else { - return childIndent.add(getWhiteSpace()); + final IndentData indent = createAlignmentIndent(childIndent, child); + if (indent != null) { + return indent; } + return childIndent.add(getWhiteSpace()); } else { return childIndent.add(myParent.getChildOffset(this, options, targetBlockStartOffset)); @@ -343,29 +343,8 @@ public abstract class AbstractBlockWrapper { } /** - * Allows to answer if indent for the given child block should be calculated taking into consideration alignment - * of the text at current block start. - * - * @param child child block to check - * @return true if indent should be calculated taking into consideration alignment of the text at current - * block start; false otherwise - */ - private boolean isIndentAffectedAlignment(AbstractBlockWrapper child) { - if (!child.getWhiteSpace().containsLineFeeds()) { - return false; - } - AlignmentImpl alignment = getAlignmentAtStartOffset(); - if (alignment == null || alignment == child.getAlignment()) { - return false; - } - - LeafBlockWrapper anchorOffsetBlock = alignment.getOffsetRespBlockBefore(child); - return anchorOffsetBlock == null || anchorOffsetBlock.getStartOffset() >= getStartOffset(); - } - - /** - * Allows to construct indent for the block that is affected by aligning rules. E.g. there is a possible case that the user - * configures method call arguments to be aligned and single parameter expression spans more than one line: + * Check if it's possible to construct indent for the block that is affected by aligning rules. E.g. there is a possible case + * that the user configures method call arguments to be aligned and single parameter expression spans more than one line: *

*

    *     public void test(String s1, String s2) {}
@@ -383,15 +362,41 @@ public abstract class AbstractBlockWrapper {
    * sub-blocks that are located on new lines should also be indented to the point of composite block start.
    * 

* This method takes care about constructing target absolute indent of the given child block assuming that it's parent - * (referenced by 'this') or it's ancestor that starts at the same offset is aligned. I.e. it assumes - * that {@link #isIndentAffectedAlignment(AbstractBlockWrapper)} returns true for the given child block. + * (referenced by 'this') or it's ancestor that starts at the same offset is aligned. * * @param indentFromParent basic indent of given child from the current parent block * @param child child block of the current aligned composite block - * @return absolute indent to use for the given child block of the current composite block + * @return absolute indent to use for the given child block of the current composite block if alignment-affected + * indent should be used for it; + * null otherwise */ + @Nullable private IndentData createAlignmentIndent(IndentData indentFromParent, AbstractBlockWrapper child) { + if (!child.getWhiteSpace().containsLineFeeds()) { + return null; + } + + AlignmentImpl alignment = getAlignmentAtStartOffset(); + if (alignment == null || alignment == child.getAlignment()) { + return null; + } + AbstractBlockWrapper previous = child.getPreviousBlock(); + LeafBlockWrapper anchorOffsetBlock = alignment.getOffsetRespBlockBefore(child); + if (anchorOffsetBlock != null && anchorOffsetBlock.getStartOffset() != getStartOffset()) { + // Located on different lines. + boolean onDifferentLines = false; + for (LeafBlockWrapper b = anchorOffsetBlock.getNextBlock(); b != null && b.getStartOffset() < getStartOffset(); b = b.getNextBlock()) { + if (b.getWhiteSpace().containsLineFeeds()) { + onDifferentLines = true; + break; + } + } + + if (!onDifferentLines) { + return null; + } + } // There is no point in continuing processing if given child is the first block, i.e. there is no alignment-implied // offset to add to the given 'indent from parent'. @@ -399,7 +404,13 @@ public abstract class AbstractBlockWrapper { return indentFromParent; } - IndentData symbolsBeforeCurrent = getNumberOfSymbolsBeforeBlock(); + IndentData symbolsBeforeCurrent; + if (anchorOffsetBlock == null) { + symbolsBeforeCurrent = getNumberOfSymbolsBeforeBlock(); + } + else { + symbolsBeforeCurrent = anchorOffsetBlock.getNumberOfSymbolsBeforeBlock(); + } // Result is calculated as a number of symbols between the current composite parent block plus given 'indent from parent'. int indentSpaces = symbolsBeforeCurrent.getIndentSpaces() + indentFromParent.getSpaces() + indentFromParent.getIndentSpaces(); From f2548d251a0ce12170cb51a11b2b7c103bfcd1e1 Mon Sep 17 00:00:00 2001 From: Sergey Evdokimov Date: Thu, 10 May 2012 15:24:36 +0400 Subject: [PATCH 16/31] remove unnecessary logging. --- .../src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java index 12337975ee83..e6181140a387 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/jar/JarFileSystemImpl.java @@ -89,7 +89,6 @@ public class JarFileSystemImpl extends JarFileSystem implements ApplicationCompo SystemInfo.isFileSystemCaseSensitive)) { VirtualFile jarRootToRefresh = markDirty(jarPath); if (jarRootToRefresh != null) { - LOG.info(jarPath + " will be refreshed due to " + event); rootsToRefresh.add(jarRootToRefresh); } } From 67bf2bb4c75f7b8a30d3ea87fa151d30c44df4c5 Mon Sep 17 00:00:00 2001 From: Dmitry Lomov Date: Tue, 8 May 2012 18:26:13 +0200 Subject: [PATCH 17/31] FileBasedIndex --- .../psi/impl/cache/impl/IdCacheTest.java | 6 +- .../intellij/psi/search/UpdateCacheTest.java | 5 +- .../intellij/find/impl/FindInProjectUtil.java | 3 +- .../impl/cache/impl/id/IdTableBuilding.java | 2 +- .../psi/impl/cache/impl/todo/TodoIndex.java | 2 +- .../com/intellij/psi/stubs/StubIndexImpl.java | 12 +- .../intellij/psi/stubs/StubUpdatingIndex.java | 2 +- .../util/indexing/FileBasedIndex.java | 2341 +---------------- .../util/indexing/FileBasedIndexImpl.java | 2327 ++++++++++++++++ .../FileBasedIndexProjectHandler.java | 4 +- .../util/indexing/UnindexedFilesUpdater.java | 6 +- .../src/componentSets/Lang.xml | 3 +- .../testFramework/LightPlatformTestCase.java | 3 +- 13 files changed, 2407 insertions(+), 2309 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java diff --git a/java/java-tests/testSrc/com/intellij/psi/impl/cache/impl/IdCacheTest.java b/java/java-tests/testSrc/com/intellij/psi/impl/cache/impl/IdCacheTest.java index 5ec392f58c23..eaf7ae618ffe 100644 --- a/java/java-tests/testSrc/com/intellij/psi/impl/cache/impl/IdCacheTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/impl/cache/impl/IdCacheTest.java @@ -21,6 +21,7 @@ import com.intellij.psi.search.UsageSearchContext; import com.intellij.testFramework.PsiTestUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.indexing.FileBasedIndex; +import com.intellij.util.indexing.FileBasedIndexImpl; import java.io.File; import java.util.Arrays; @@ -35,9 +36,8 @@ public class IdCacheTest extends CodeInsightTestCase{ protected void setUp() throws Exception { super.setUp(); - final FileBasedIndex fileBasedIndex = FileBasedIndex.getInstance(); - fileBasedIndex.requestRebuild(IdIndex.NAME); - fileBasedIndex.requestRebuild(TodoIndex.NAME); + FileBasedIndex.getInstance().requestRebuild(IdIndex.NAME); + FileBasedIndex.getInstance().requestRebuild(TodoIndex.NAME); String root = JavaTestUtil.getJavaTestDataPath()+ "/psi/impl/cache/"; diff --git a/java/java-tests/testSrc/com/intellij/psi/search/UpdateCacheTest.java b/java/java-tests/testSrc/com/intellij/psi/search/UpdateCacheTest.java index cd2cf6890ac3..be90964eafae 100644 --- a/java/java-tests/testSrc/com/intellij/psi/search/UpdateCacheTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/search/UpdateCacheTest.java @@ -44,6 +44,7 @@ import com.intellij.testFramework.PsiTestUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.Processor; import com.intellij.util.indexing.FileBasedIndex; +import com.intellij.util.indexing.FileBasedIndexImpl; import org.jetbrains.annotations.NonNls; import java.io.File; @@ -55,8 +56,8 @@ public class UpdateCacheTest extends PsiTestCase{ protected void setUp() throws Exception { super.setUp(); - FileBasedIndex.requestRebuild(IdIndex.NAME); - FileBasedIndex.requestRebuild(TodoIndex.NAME); + FileBasedIndex.getInstance().requestRebuild(IdIndex.NAME); + FileBasedIndex.getInstance().requestRebuild(TodoIndex.NAME); } @Override diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java b/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java index a9c2eb118461..e5601ebb1b87 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java @@ -502,8 +502,7 @@ public class FindInProjectUtil { if (!keys.isEmpty()) { fast = true; List hits = new ArrayList(); - FileBasedIndex.getInstance() - .getFilesWithKey(TrigramIndex.INDEX_ID, keys, new CommonProcessors.CollectProcessor(hits), scope); + FileBasedIndex.getInstance().getFilesWithKey(TrigramIndex.INDEX_ID, keys, new CommonProcessors.CollectProcessor(hits), scope); for (VirtualFile hit : hits) { resultFiles.add(pm.findFile(hit)); diff --git a/platform/lang-impl/src/com/intellij/psi/impl/cache/impl/id/IdTableBuilding.java b/platform/lang-impl/src/com/intellij/psi/impl/cache/impl/id/IdTableBuilding.java index 8e09281d2675..bb8dc7f3104f 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/cache/impl/id/IdTableBuilding.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/cache/impl/id/IdTableBuilding.java @@ -322,7 +322,7 @@ public class IdTableBuilding { final OccurrenceConsumer occurrenceConsumer = new OccurrenceConsumer(null, true); EditorHighlighter highlighter; - final EditorHighlighter editorHighlighter = inputData.getUserData(FileBasedIndex.EDITOR_HIGHLIGHTER); + final EditorHighlighter editorHighlighter = inputData.getUserData(FileBasedIndexImpl.EDITOR_HIGHLIGHTER); if (editorHighlighter != null && checkCanUseCachedEditorHighlighter(chars, editorHighlighter)) { highlighter = editorHighlighter; } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/cache/impl/todo/TodoIndex.java b/platform/lang-impl/src/com/intellij/psi/impl/cache/impl/todo/TodoIndex.java index 594436346a7d..e4c91b7904da 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/cache/impl/todo/TodoIndex.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/cache/impl/todo/TodoIndex.java @@ -54,7 +54,7 @@ public class TodoIndex extends FileBasedIndexExtension messageBus.connect().subscribe(IndexPatternProvider.INDEX_PATTERNS_CHANGED, new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { - FileBasedIndex.requestRebuild(NAME); + FileBasedIndex.getInstance().requestRebuild(NAME); } }); } diff --git a/platform/lang-impl/src/com/intellij/psi/stubs/StubIndexImpl.java b/platform/lang-impl/src/com/intellij/psi/stubs/StubIndexImpl.java index d11e89e0f5a7..05a5e6d08e00 100644 --- a/platform/lang-impl/src/com/intellij/psi/stubs/StubIndexImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/stubs/StubIndexImpl.java @@ -200,7 +200,7 @@ public class StubIndexImpl extends StubIndex implements ApplicationComponent, Pe @NotNull final Project project, @Nullable final GlobalSearchScope scope, @NotNull final Processor processor) { - final FileBasedIndex fileBasedIndex = FileBasedIndex.getInstance(); + final FileBasedIndexImpl fileBasedIndex = (FileBasedIndexImpl)FileBasedIndex.getInstance(); fileBasedIndex.ensureUpToDate(StubUpdatingIndex.INDEX_ID, project, scope); final PersistentFS fs = (PersistentFS)ManagingFS.getInstance(); @@ -211,11 +211,11 @@ public class StubIndexImpl extends StubIndex implements ApplicationComponent, Pe try { try { // disable up-to-date check to avoid locks on attempt to acquire index write lock while holding at the same time the readLock for this index - FileBasedIndex.disableUpToDateCheckForCurrentThread(); + FileBasedIndexImpl.disableUpToDateCheckForCurrentThread(); index.getReadLock().lock(); final ValueContainer container = index.getData(key); - final FileBasedIndex.ProjectIndexableFilesFilter projectFilesFilter = fileBasedIndex.projectIndexableFiles(project); + final FileBasedIndexImpl.ProjectIndexableFilesFilter projectFilesFilter = fileBasedIndex.projectIndexableFiles(project); return container.forEach(new ValueContainer.ContainerAction() { @Override @@ -320,14 +320,14 @@ public class StubIndexImpl extends StubIndex implements ApplicationComponent, Pe } finally { index.getReadLock().unlock(); - FileBasedIndex.enableUpToDateCheckForCurrentThread(); + FileBasedIndexImpl.enableUpToDateCheckForCurrentThread(); } } catch (StorageException e) { forceRebuild(e); } catch (RuntimeException e) { - final Throwable cause = FileBasedIndex.getCauseToRebuildIndex(e); + final Throwable cause = FileBasedIndexImpl.getCauseToRebuildIndex(e); if (cause != null) { forceRebuild(cause); } @@ -354,7 +354,7 @@ public class StubIndexImpl extends StubIndex implements ApplicationComponent, Pe } private static void requestRebuild() { - FileBasedIndex.requestRebuild(StubUpdatingIndex.INDEX_ID); + FileBasedIndex.getInstance().requestRebuild(StubUpdatingIndex.INDEX_ID); } @Override diff --git a/platform/lang-impl/src/com/intellij/psi/stubs/StubUpdatingIndex.java b/platform/lang-impl/src/com/intellij/psi/stubs/StubUpdatingIndex.java index 40fa9d8ac2cd..428f29dd556e 100644 --- a/platform/lang-impl/src/com/intellij/psi/stubs/StubUpdatingIndex.java +++ b/platform/lang-impl/src/com/intellij/psi/stubs/StubUpdatingIndex.java @@ -232,7 +232,7 @@ public class StubUpdatingIndex extends CustomImplementationFileBasedIndexExtensi } catch (StorageException e) { LOG.info(e); - FileBasedIndex.requestRebuild(INDEX_ID); + FileBasedIndex.getInstance().requestRebuild(INDEX_ID); } } diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java index 6e8bcf2a307b..c2f3ad423b19 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java @@ -13,1676 +13,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.intellij.util.indexing; -import com.intellij.AppTopics; -import com.intellij.history.LocalHistory; -import com.intellij.ide.caches.CacheUpdater; -import com.intellij.lang.ASTNode; -import com.intellij.notification.NotificationDisplayType; -import com.intellij.notification.NotificationGroup; -import com.intellij.notification.NotificationType; -import com.intellij.openapi.application.ApplicationAdapter; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.PathManager; import com.intellij.openapi.components.ApplicationComponent; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.highlighter.EditorHighlighter; -import com.intellij.openapi.editor.impl.EditorHighlighterCache; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter; -import com.intellij.openapi.fileTypes.*; -import com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.progress.*; -import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator; -import com.intellij.openapi.project.*; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.vfs.*; -import com.intellij.openapi.vfs.ex.VirtualFileManagerEx; -import com.intellij.openapi.vfs.newvfs.BulkFileListener; -import com.intellij.openapi.vfs.newvfs.ManagingFS; -import com.intellij.openapi.vfs.newvfs.NewVirtualFile; -import com.intellij.openapi.vfs.newvfs.events.VFileEvent; -import com.intellij.openapi.vfs.newvfs.persistent.FlushingDaemon; -import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; -import com.intellij.psi.*; -import com.intellij.psi.impl.PsiDocumentTransactionListener; -import com.intellij.psi.impl.source.PsiFileImpl; -import com.intellij.psi.search.EverythingGlobalScope; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileWithId; import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.stubs.SerializationManager; -import com.intellij.util.*; -import com.intellij.util.concurrency.Semaphore; -import com.intellij.util.containers.ConcurrentHashSet; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.io.*; -import com.intellij.util.io.DataOutputStream; -import com.intellij.util.io.storage.HeavyProcessLatch; -import com.intellij.util.messages.MessageBus; -import com.intellij.util.messages.MessageBusConnection; -import gnu.trove.*; +import com.intellij.util.Processor; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.*; -import java.io.*; -import java.lang.ref.SoftReference; -import java.util.*; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.Collection; +import java.util.List; +import java.util.Set; /** - * @author Eugene Zhuravlev - * Date: Dec 20, 2007 + * Author: dmitrylomov */ - -public class FileBasedIndex implements ApplicationComponent { - private static final Logger LOG = Logger.getInstance("#com.intellij.util.indexing.FileBasedIndex"); - @NonNls - private static final String CORRUPTION_MARKER_NAME = "corruption.marker"; - private final Map, Pair, InputFilter>> myIndices = new THashMap, Pair, InputFilter>>(); - private final Map, Semaphore> myUnsavedDataIndexingSemaphores = new THashMap, Semaphore>(); - private final TObjectIntHashMap> myIndexIdToVersionMap = new TObjectIntHashMap>(); - private final Set> myNotRequiringContentIndices = new THashSet>(); - private final Set> myRequiringContentIndices = new THashSet>(); - private final Set myNoLimitCheckTypes = new THashSet(); - - private final PerIndexDocumentVersionMap myLastIndexedDocStamps = new PerIndexDocumentVersionMap(); - @NotNull private final ChangedFilesCollector myChangedFilesCollector; - - private final List myIndexableSets = ContainerUtil.createEmptyCOWList(); - private final Map myIndexableSetToProjectMap = new THashMap(); - - private static final int OK = 1; - private static final int REQUIRES_REBUILD = 2; - private static final int REBUILD_IN_PROGRESS = 3; - private static final Map, AtomicInteger> ourRebuildStatus = new THashMap, AtomicInteger>(); - - private final VirtualFileManagerEx myVfManager; - private final FileDocumentManager myFileDocumentManager; - private final FileTypeManager myFileTypeManager; - private final ConcurrentHashSet> myUpToDateIndices = new ConcurrentHashSet>(); - private final Map myTransactionMap = new THashMap(); - - private static final int ALREADY_PROCESSED = 0x04; - - @Nullable private final String myConfigPath; - @Nullable private final String mySystemPath; - private final boolean myIsUnitTestMode; - @Nullable private ScheduledFuture myFlushingFuture; - private volatile int myLocalModCount; - private volatile int myFilesModCount; - - public void requestReindex(@NotNull final VirtualFile file) { - myChangedFilesCollector.invalidateIndices(file, true); - } - - public void requestReindexExcluded(@NotNull final VirtualFile file) { - myChangedFilesCollector.invalidateIndices(file, false); - } - - public FileBasedIndex(final VirtualFileManagerEx vfManager, FileDocumentManager fdm, - FileTypeManager fileTypeManager, @NotNull MessageBus bus, SerializationManager sm /*need this parameter to ensure component dependency*/) throws IOException { - myVfManager = vfManager; - myFileDocumentManager = fdm; - myFileTypeManager = fileTypeManager; - myIsUnitTestMode = ApplicationManager.getApplication().isUnitTestMode(); - myConfigPath = calcConfigPath(PathManager.getConfigPath()); - mySystemPath = calcConfigPath(PathManager.getSystemPath()); - - final MessageBusConnection connection = bus.connect(); - connection.subscribe(PsiDocumentTransactionListener.TOPIC, new PsiDocumentTransactionListener() { - @Override - public void transactionStarted(final Document doc, final PsiFile file) { - if (file != null) { - synchronized (myTransactionMap) { - myTransactionMap.put(doc, file); - } - myUpToDateIndices.clear(); - } - } - - @Override - public void transactionCompleted(final Document doc, final PsiFile file) { - synchronized (myTransactionMap) { - myTransactionMap.remove(doc); - } - } - }); - - connection.subscribe(FileTypeManager.TOPIC, new FileTypeListener() { - @Nullable private Map> myTypeToExtensionMap; - @Override - public void beforeFileTypesChanged(final FileTypeEvent event) { - cleanupProcessedFlag(); - myTypeToExtensionMap = new THashMap>(); - for (FileType type : myFileTypeManager.getRegisteredFileTypes()) { - myTypeToExtensionMap.put(type, getExtensions(type)); - } - } - - @Override - public void fileTypesChanged(final FileTypeEvent event) { - final Map> oldExtensions = myTypeToExtensionMap; - myTypeToExtensionMap = null; - if (oldExtensions != null) { - final Map> newExtensions = new THashMap>(); - for (FileType type : myFileTypeManager.getRegisteredFileTypes()) { - newExtensions.put(type, getExtensions(type)); - } - // we are interested only in extension changes or removals. - // addition of an extension is handled separately by RootsChanged event - if (!newExtensions.keySet().containsAll(oldExtensions.keySet())) { - rebuildAllIndices(); - return; - } - for (Map.Entry> entry : oldExtensions.entrySet()) { - FileType fileType = entry.getKey(); - Set strings = entry.getValue(); - if (!newExtensions.get(fileType).containsAll(strings)) { - rebuildAllIndices(); - return; - } - } - } - } - - @NotNull - private Set getExtensions(@NotNull FileType type) { - final Set set = new THashSet(); - for (FileNameMatcher matcher : myFileTypeManager.getAssociations(type)) { - set.add(matcher.getPresentableString()); - } - return set; - } - - private void rebuildAllIndices() { - for (ID indexId : myIndices.keySet()) { - try { - clearIndex(indexId); - } - catch (StorageException e) { - LOG.info(e); - } - } - scheduleIndexRebuild(true); - } - }); - - connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() { - @Override - public void before(@NotNull List events) { - for (VFileEvent event : events) { - final Object requestor = event.getRequestor(); - if (requestor instanceof FileDocumentManager || requestor instanceof PsiManager || requestor == LocalHistory.VFS_EVENT_REQUESTOR) { - cleanupMemoryStorage(); - break; - } - } - } - - @Override - public void after(@NotNull List events) { - } - }); - - connection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerAdapter() { - @Override - public void fileContentReloaded(VirtualFile file, @NotNull Document document) { - cleanupMemoryStorage(); - } - - @Override - public void unsavedDocumentsDropped() { - cleanupMemoryStorage(); - } - }); - - ApplicationManager.getApplication().addApplicationListener(new ApplicationAdapter() { - @Override - public void writeActionStarted(Object action) { - myUpToDateIndices.clear(); - } - }); - - myChangedFilesCollector = new ChangedFilesCollector(); - - /* - final File workInProgressFile = getMarkerFile(); - if (workInProgressFile.exists()) { - // previous IDEA session was closed incorrectly, so drop all indices - FileUtil.delete(PathManager.getIndexRoot()); - } - */ - - try { - final FileBasedIndexExtension[] extensions = Extensions.getExtensions(FileBasedIndexExtension.EXTENSION_POINT_NAME); - for (FileBasedIndexExtension extension : extensions) { - ourRebuildStatus.put(extension.getName(), new AtomicInteger(OK)); - } - - final File corruptionMarker = new File(PathManager.getIndexRoot(), CORRUPTION_MARKER_NAME); - final boolean currentVersionCorrupted = corruptionMarker.exists(); - boolean versionChanged = false; - for (FileBasedIndexExtension extension : extensions) { - versionChanged |= registerIndexer(extension, currentVersionCorrupted); - } - FileUtil.delete(corruptionMarker); - - String rebuildNotification = null; - if (currentVersionCorrupted) { - rebuildNotification = "Index files on disk are corrupted. Indices will be rebuilt."; - } - else if (versionChanged) { - rebuildNotification = "Index file format has changed for some indices. These indices will be rebuilt."; - } - if (rebuildNotification != null - && !ApplicationManager.getApplication().isHeadlessEnvironment() - && Registry.is("ide.showIndexRebuildMessage")) { - new NotificationGroup("Indexing", NotificationDisplayType.BALLOON, false) - .createNotification("Index Rebuild", rebuildNotification, NotificationType.INFORMATION, null).notify(null); - } - - dropUnregisteredIndices(); - - // check if rebuild was requested for any index during registration - for (ID indexId : myIndices.keySet()) { - if (ourRebuildStatus.get(indexId).compareAndSet(REQUIRES_REBUILD, OK)) { - try { - clearIndex(indexId); - } - catch (StorageException e) { - requestRebuild(indexId); - LOG.error(e); - } - } - } - - myVfManager.addVirtualFileListener(myChangedFilesCollector); - - registerIndexableSet(new AdditionalIndexableFileSet(), null); - } - finally { - ShutDownTracker.getInstance().registerShutdownTask(new Runnable() { - @Override - public void run() { - performShutdown(); - } - }); - //FileUtil.createIfDoesntExist(workInProgressFile); - saveRegisteredIndices(myIndices.keySet()); - myFlushingFuture = FlushingDaemon.everyFiveSeconds(new Runnable() { - int lastModCount = 0; - @Override - public void run() { - if (lastModCount == myLocalModCount) { - flushAllIndices(lastModCount); - } - lastModCount = myLocalModCount; - } - }); - - } - } - - @Override - public void initComponent() { - } - - @Nullable - private static String calcConfigPath(final String path) { - try { - final String _path = FileUtil.toSystemIndependentName(new File(path).getCanonicalPath()); - return _path.endsWith("/")? _path : _path + "/" ; - } - catch (IOException e) { - LOG.info(e); - return null; - } - } - - private static class FileBasedIndexHolder { - private static final FileBasedIndex ourInstance = ApplicationManager.getApplication().getComponent(FileBasedIndex.class); - } - +public abstract class FileBasedIndex implements ApplicationComponent { public static FileBasedIndex getInstance() { - return FileBasedIndexHolder.ourInstance; - } - - /** - * @return true if registered index requires full rebuild for some reason, e.g. is just created or corrupted - * - * @param extension - * @param isCurrentVersionCorrupted - */ - private boolean registerIndexer(@NotNull final FileBasedIndexExtension extension, final boolean isCurrentVersionCorrupted) throws IOException { - final ID name = extension.getName(); - final int version = extension.getVersion(); - final File versionFile = IndexInfrastructure.getVersionFile(name); - final boolean versionFileExisted = versionFile.exists(); - boolean versionChanged = false; - if (isCurrentVersionCorrupted || IndexInfrastructure.versionDiffers(versionFile, version)) { - if (!isCurrentVersionCorrupted && versionFileExisted) { - versionChanged = true; - LOG.info("Version has changed for index " + name + ". The index will be rebuilt."); - } - FileUtil.delete(IndexInfrastructure.getIndexRootDir(name)); - IndexInfrastructure.rewriteVersion(versionFile, version); - } - - for (int attempt = 0; attempt < 2; attempt++) { - try { - final MapIndexStorage storage = new MapIndexStorage(IndexInfrastructure.getStorageFile(name), extension.getKeyDescriptor(), extension.getValueExternalizer(), extension.getCacheSize()); - final MemoryIndexStorage memStorage = new MemoryIndexStorage(storage); - final UpdatableIndex index = createIndex(name, extension, memStorage); - final InputFilter inputFilter = extension.getInputFilter(); - - assert inputFilter != null : "Index extension " + name + " must provide non-null input filter"; - - myIndices.put(name, new Pair, InputFilter>(index, new IndexableFilesFilter(inputFilter))); - myUnsavedDataIndexingSemaphores.put(name, new Semaphore()); - myIndexIdToVersionMap.put(name, version); - if (!extension.dependsOnFileContent()) { - myNotRequiringContentIndices.add(name); - } - else { - myRequiringContentIndices.add(name); - } - myNoLimitCheckTypes.addAll(extension.getFileTypesWithSizeLimitNotApplicable()); - break; - } - catch (IOException e) { - LOG.info(e); - FileUtil.delete(IndexInfrastructure.getIndexRootDir(name)); - IndexInfrastructure.rewriteVersion(versionFile, version); - } - } - return versionChanged; - } - - private static void saveRegisteredIndices(@NotNull Collection> ids) { - final File file = getRegisteredIndicesFile(); - try { - FileUtil.createIfDoesntExist(file); - final DataOutputStream os = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); - try { - os.writeInt(ids.size()); - for (ID id : ids) { - IOUtil.writeString(id.toString(), os); - } - } - finally { - os.close(); - } - } - catch (IOException ignored) { - } - } - - @NotNull - private static Set readRegisteredIndexNames() { - final Set result = new THashSet(); - try { - final DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(getRegisteredIndicesFile()))); - try { - final int size = in.readInt(); - for (int idx = 0; idx < size; idx++) { - result.add(IOUtil.readString(in)); - } - } - finally { - in.close(); - } - } - catch (IOException ignored) { - } - return result; - } - - @NotNull - private static File getRegisteredIndicesFile() { - return new File(PathManager.getIndexRoot(), "registered"); - } - - @NotNull - private UpdatableIndex createIndex(@NotNull final ID indexId, @NotNull final FileBasedIndexExtension extension, @NotNull final MemoryIndexStorage storage) throws IOException { - final MapReduceIndex index; - if (extension instanceof CustomImplementationFileBasedIndexExtension) { - final UpdatableIndex custom = ((CustomImplementationFileBasedIndexExtension)extension).createIndexImplementation(indexId, this, storage); - - assert custom != null : "Custom index implementation must not be null; index: " + indexId; - - if (!(custom instanceof MapReduceIndex)) { - return custom; - } - index = (MapReduceIndex)custom; - } - else { - index = new MapReduceIndex(indexId, extension.getIndexer(), storage); - } - - final KeyDescriptor keyDescriptor = extension.getKeyDescriptor(); - index.setInputIdToDataKeysIndex(new Factory>>() { - @Override - public PersistentHashMap> create() { - try { - return createIdToDataKeysIndex(indexId, keyDescriptor, storage); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - }); - - return index; - } - - @NotNull - private static PersistentHashMap> createIdToDataKeysIndex(@NotNull final ID indexId, - @NotNull final KeyDescriptor keyDescriptor, - @NotNull MemoryIndexStorage storage) throws IOException { - final File indexStorageFile = IndexInfrastructure.getInputIndexStorageFile(indexId); - final Ref isBufferingMode = new Ref(false); - final TIntObjectHashMap> tempMap = new TIntObjectHashMap>(); - - final DataExternalizer> dataExternalizer = new DataExternalizer>() { - @Override - public void save(DataOutput out, @NotNull Collection value) throws IOException { - try { - DataInputOutputUtil.writeINT(out, value.size()); - for (K key : value) { - keyDescriptor.save(out, key); - } - } - catch (IllegalArgumentException e) { - throw new IOException("Error saving data for index " + indexId, e); - } - } - - @NotNull - @Override - public Collection read(DataInput in) throws IOException { - try { - final int size = DataInputOutputUtil.readINT(in); - final List list = new ArrayList(size); - for (int idx = 0; idx < size; idx++) { - list.add(keyDescriptor.read(in)); - } - return list; - } - catch (IllegalArgumentException e) { - throw new IOException("Error reading data for index " + indexId, e); - } - } - }; - - // Important! Update IdToDataKeysIndex depending on the sate of "buffering" flag from the MemoryStorage. - // If buffering is on, all changes should be done in memory (similar to the way it is done in memory storage). - // Otherwise data in IdToDataKeysIndex will not be in sync with the 'main' data in the index on disk and index updates will be based on the - // wrong sets of keys for the given file. This will lead to unpredictable results in main index because it will not be - // cleared properly before updating (removed data will still be present on disk). See IDEA-52223 for illustration of possible effects. - - final PersistentHashMap> map = new PersistentHashMap>( - indexStorageFile, EnumeratorIntegerDescriptor.INSTANCE, dataExternalizer - ) { - - @Override - protected Collection doGet(Integer integer) throws IOException { - if (isBufferingMode.get()) { - final Collection collection = tempMap.get(integer); - if (collection != null) { - return collection; - } - } - return super.doGet(integer); - } - - @Override - protected void doPut(Integer integer, @Nullable Collection ks) throws IOException { - if (isBufferingMode.get()) { - tempMap.put(integer, ks == null? Collections.emptySet() : ks); - } - else { - super.doPut(integer, ks); - } - } - - @Override - protected void doRemove(Integer integer) throws IOException { - if (isBufferingMode.get()) { - tempMap.put(integer, Collections.emptySet()); - } - else { - super.doRemove(integer); - } - } - }; - - storage.addBufferingStateListsner(new MemoryIndexStorage.BufferingStateListener() { - @Override - public void bufferingStateChanged(boolean newState) { - synchronized (map) { - isBufferingMode.set(newState); - } - } - @Override - public void memoryStorageCleared() { - synchronized (map) { - tempMap.clear(); - } - } - }); - return map; - } - - @Override - @NonNls - @NotNull - public String getComponentName() { - return "FileBasedIndex"; - } - - @Override - public void disposeComponent() { - performShutdown(); - } - - private final AtomicBoolean myShutdownPerformed = new AtomicBoolean(false); - - private void performShutdown() { - if (!myShutdownPerformed.compareAndSet(false, true)) { - return; // already shut down - } - try { - if (myFlushingFuture != null) { - myFlushingFuture.cancel(false); - myFlushingFuture = null; - } - - myFileDocumentManager.saveAllDocuments(); - } - finally { - LOG.info("START INDEX SHUTDOWN"); - try { - myChangedFilesCollector.forceUpdate(null, null, null, true); - - for (ID indexId : myIndices.keySet()) { - final UpdatableIndex index = getIndex(indexId); - assert index != null; - checkRebuild(indexId, true); // if the index was scheduled for rebuild, only clean it - //LOG.info("DISPOSING " + indexId); - index.dispose(); - } - - myVfManager.removeVirtualFileListener(myChangedFilesCollector); - - //FileUtil.delete(getMarkerFile()); - } - catch (Throwable e) { - LOG.info("Problems during index shutdown", e); - throw new RuntimeException(e); - } - LOG.info("END INDEX SHUTDOWN"); - } - } - - private void flushAllIndices(final long modCount) { - if (HeavyProcessLatch.INSTANCE.isRunning()) { - return; - } - IndexingStamp.flushCache(); - for (ID indexId : new ArrayList>(myIndices.keySet())) { - if (HeavyProcessLatch.INSTANCE.isRunning() || modCount != myLocalModCount) { - return; // do not interfere with 'main' jobs - } - try { - final UpdatableIndex index = getIndex(indexId); - if (index != null) { - index.flush(); - } - } - catch (StorageException e) { - LOG.info(e); - requestRebuild(indexId); - } - } - - if (!HeavyProcessLatch.INSTANCE.isRunning() && modCount == myLocalModCount) { // do not interfere with 'main' jobs - SerializationManager.getInstance().flushNameStorage(); - } - } - - /** - * @param project it is guaranteed to return data which is up-to-date withing the project - * Keys obtained from the files which do not belong to the project specified may not be up-to-date or even exist - */ - @NotNull - public Collection getAllKeys(@NotNull final ID indexId, @NotNull Project project) { - Set allKeys = new THashSet(); - processAllKeys(indexId, new CommonProcessors.CollectProcessor(allKeys), project); - return allKeys; - } - - /** - * @param project it is guaranteed to return data which is up-to-date withing the project - * Keys obtained from the files which do not belong to the project specified may not be up-to-date or even exist - */ - public boolean processAllKeys(@NotNull final ID indexId, Processor processor, @Nullable Project project) { - try { - final UpdatableIndex index = getIndex(indexId); - if (index == null) { - return true; - } - ensureUpToDate(indexId, project, project != null? GlobalSearchScope.allScope(project) : new EverythingGlobalScope()); - return index.processAllKeys(processor); - } - catch (StorageException e) { - scheduleRebuild(indexId, e); - } - catch (RuntimeException e) { - final Throwable cause = e.getCause(); - if (cause instanceof StorageException || cause instanceof IOException) { - scheduleRebuild(indexId, cause); - } - else { - throw e; - } - } - - return false; - } - - private static final ThreadLocal myUpToDateCheckState = new ThreadLocal(); - - public static void disableUpToDateCheckForCurrentThread() { - final Integer currentValue = myUpToDateCheckState.get(); - myUpToDateCheckState.set(currentValue == null? 1 : currentValue.intValue() + 1); - } - - public static void enableUpToDateCheckForCurrentThread() { - final Integer currentValue = myUpToDateCheckState.get(); - if (currentValue != null) { - final int newValue = currentValue.intValue() - 1; - if (newValue != 0) { - myUpToDateCheckState.set(newValue); - } - else { - myUpToDateCheckState.remove(); - } - } - } - - private static boolean isUpToDateCheckEnabled() { - final Integer value = myUpToDateCheckState.get(); - return value == null || value.intValue() == 0; - } - - - private final ThreadLocal myReentrancyGuard = new ThreadLocal() { - @Override - protected Boolean initialValue() { - return Boolean.FALSE; - } - }; - - /** - * DO NOT CALL DIRECTLY IN CLIENT CODE - * The method is internal to indexing engine end is called internally. The method is public due to implementation details - */ - public void ensureUpToDate(@NotNull final ID indexId, @Nullable Project project, @Nullable GlobalSearchScope filter) { - ensureUpToDate(indexId, project, filter, null); - } - - private void ensureUpToDate(@NotNull final ID indexId, @Nullable Project project, @Nullable GlobalSearchScope filter, - @Nullable VirtualFile restrictedFile) { - if (!needsFileContentLoading(indexId)) { - return; //indexed eagerly in foreground while building unindexed file list - } - if (isDumb(project)) { - handleDumbMode(project); - } - - if (myReentrancyGuard.get().booleanValue()) { - //assert false : "ensureUpToDate() is not reentrant!"; - return; - } - myReentrancyGuard.set(Boolean.TRUE); - - try { - myChangedFilesCollector.ensureAllInvalidateTasksCompleted(); - if (isUpToDateCheckEnabled()) { - try { - checkRebuild(indexId, false); - myChangedFilesCollector.forceUpdate(project, filter, restrictedFile, false); - indexUnsavedDocuments(indexId, project, filter, restrictedFile); - } - catch (StorageException e) { - scheduleRebuild(indexId, e); - } - catch (RuntimeException e) { - final Throwable cause = e.getCause(); - if (cause instanceof StorageException || cause instanceof IOException) { - scheduleRebuild(indexId, e); - } - else { - throw e; - } - } - } - } - finally { - myReentrancyGuard.set(Boolean.FALSE); - } - } - - private static void handleDumbMode(@Nullable Project project) { - ProgressManager.checkCanceled(); // DumbModeAction.CANCEL - - if (project != null) { - final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); - if (progressIndicator instanceof BackgroundableProcessIndicator) { - final BackgroundableProcessIndicator indicator = (BackgroundableProcessIndicator)progressIndicator; - if (indicator.getDumbModeAction() == DumbModeAction.WAIT) { - assert !ApplicationManager.getApplication().isDispatchThread(); - DumbService.getInstance(project).waitForSmartMode(); - return; - } - } - } - - throw new IndexNotReadyException(); - } - - private static boolean isDumb(@Nullable Project project) { - if (project != null) { - return DumbServiceImpl.getInstance(project).isDumb(); - } - for (Project proj : ProjectManager.getInstance().getOpenProjects()) { - if (DumbServiceImpl.getInstance(proj).isDumb()) { - return true; - } - } - return false; - } - - @NotNull - public List getValues(@NotNull final ID indexId, @NotNull K dataKey, @NotNull final GlobalSearchScope filter) { - final List values = new SmartList(); - processValuesImpl(indexId, dataKey, true, null, new ValueProcessor() { - @Override - public boolean process(final VirtualFile file, final V value) { - values.add(value); - return true; - } - }, filter); - return values; - } - - @NotNull - public Collection getContainingFiles(@NotNull final ID indexId, @NotNull K dataKey, @NotNull final GlobalSearchScope filter) { - final Set files = new THashSet(); - processValuesImpl(indexId, dataKey, false, null, new ValueProcessor() { - @Override - public boolean process(final VirtualFile file, final V value) { - files.add(file); - return true; - } - }, filter); - return files; - } - - - public interface ValueProcessor { - /** - * @param value a value to process - * @param file the file the value came from - * @return false if no further processing is needed, true otherwise - */ - boolean process(VirtualFile file, V value); - } - - /** - * @return false if ValueProcessor.process() returned false; true otherwise or if ValueProcessor was not called at all - */ - public boolean processValues(@NotNull final ID indexId, @NotNull final K dataKey, @Nullable final VirtualFile inFile, - @NotNull ValueProcessor processor, @NotNull final GlobalSearchScope filter) { - return processValuesImpl(indexId, dataKey, false, inFile, processor, filter); - } - - - - - @Nullable - private R processExceptions(@NotNull final ID indexId, - @Nullable final VirtualFile restrictToFile, - @NotNull final GlobalSearchScope filter, - @NotNull ThrowableConvertor, R, StorageException> computable) { - try { - final UpdatableIndex index = getIndex(indexId); - if (index == null) { - return null; - } - final Project project = filter.getProject(); - //assert project != null : "GlobalSearchScope#getProject() should be not-null for all index queries"; - ensureUpToDate(indexId, project, filter, restrictToFile); - - try { - index.getReadLock().lock(); - return computable.convert(index); - } - finally { - index.getReadLock().unlock(); - } - } - catch (StorageException e) { - scheduleRebuild(indexId, e); - } - catch (RuntimeException e) { - final Throwable cause = getCauseToRebuildIndex(e); - if (cause != null) { - scheduleRebuild(indexId, cause); - } - else { - throw e; - } - } - return null; - } - - private boolean processValuesImpl(@NotNull final ID indexId, final K dataKey, final boolean ensureValueProcessedOnce, - @Nullable final VirtualFile restrictToFile, @NotNull final ValueProcessor processor, - @NotNull final GlobalSearchScope filter) { - ThrowableConvertor, Boolean, StorageException> keyProcessor = new ThrowableConvertor, Boolean, StorageException>() { - @Override - public Boolean convert(@NotNull UpdatableIndex index) throws StorageException { - final ValueContainer container = index.getData(dataKey); - - boolean shouldContinue = true; - - if (restrictToFile != null) { - if (restrictToFile instanceof VirtualFileWithId) { - final int restrictedFileId = getFileId(restrictToFile); - for (final Iterator valueIt = container.getValueIterator(); valueIt.hasNext(); ) { - final V value = valueIt.next(); - if (container.isAssociated(value, restrictedFileId)) { - shouldContinue = processor.process(restrictToFile, value); - if (!shouldContinue) { - break; - } - } - } - } - } - else { - final PersistentFS fs = (PersistentFS)ManagingFS.getInstance(); - ProjectIndexableFilesFilter projectFilesSet = projectIndexableFiles(filter.getProject()); - VALUES_LOOP: for (final Iterator valueIt = container.getValueIterator(); valueIt.hasNext();) { - final V value = valueIt.next(); - for (final ValueContainer.IntIterator inputIdsIterator = container.getInputIdsIterator(value); inputIdsIterator.hasNext();) { - final int id = inputIdsIterator.next(); - if (projectFilesSet != null && !projectFilesSet.contains(id)) continue; - VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id); - if (file != null && filter.accept(file)) { - shouldContinue = processor.process(file, value); - if (!shouldContinue) { - break VALUES_LOOP; - } - if (ensureValueProcessedOnce) { - break; // continue with the next value - } - } - } - } - } - return shouldContinue; - } - }; - final Boolean result = processExceptions(indexId, restrictToFile, filter, keyProcessor); - return result == null || result.booleanValue(); - } - - public boolean processFilesContainingAllKeys(@NotNull final ID indexId, - @NotNull final Collection dataKeys, - @NotNull final GlobalSearchScope filter, - @Nullable Condition valueChecker, - @NotNull final Processor processor) { - ProjectIndexableFilesFilter filesSet = projectIndexableFiles(filter.getProject()); - final TIntHashSet set = collectFileIdsContainingAllKeys(indexId, dataKeys, filter, valueChecker, filesSet); - return set != null && processVirtualFiles(set, filter, processor); - } - - private static final Key> ourProjectFilesSetKey = Key.create("projectFiles"); - - public static final class ProjectIndexableFilesFilter { - private static final int SHIFT = 6; - private static final int MASK = (1 << SHIFT) - 1; - private final long[] myBitMask; - private final int myModificationCount; - private final int myMinId; - private final int myMaxId; - - private ProjectIndexableFilesFilter(@NotNull TIntHashSet set, int modificationCount) { - myModificationCount = modificationCount; - final int[] minMax = new int[2]; - if (set.size() > 0) { - minMax[0] = minMax[1] = set.iterator().next(); - } - set.forEach(new TIntProcedure() { - @Override - public boolean execute(int value) { - minMax[0] = Math.min(minMax[0], value); - minMax[1] = Math.max(minMax[1], value); - return true; - } - }); - myMaxId = minMax[1]; - myMinId = minMax[0]; - myBitMask = new long[((myMaxId - myMinId) >> SHIFT) + 1]; - set.forEach(new TIntProcedure() { - @Override - public boolean execute(int value) { - value = value - myMinId; - myBitMask[value >> SHIFT] |= (1L << (value & MASK)); - return true; - } - }); - } - - public boolean contains(int id) { - if (id < myMinId) return false; - if (id > myMaxId) return false; - id -= myMinId; - return (myBitMask[id >> SHIFT] & (1L << (id & MASK))) != 0; - } - } - - @Nullable - public ProjectIndexableFilesFilter projectIndexableFiles(@Nullable Project project) { - if (project == null) return null; - - SoftReference reference = project.getUserData(ourProjectFilesSetKey); - ProjectIndexableFilesFilter data = reference != null ? reference.get() : null; - if (data != null && data.myModificationCount == myFilesModCount) return data; - - final TIntHashSet filesSet = new TIntHashSet(); - iterateIndexableFiles(new ContentIterator() { - @Override - public boolean processFile(@NotNull VirtualFile fileOrDir) { - filesSet.add(((VirtualFileWithId)fileOrDir).getId()); - return true; - } - }, project, ProgressManager.getInstance().getProgressIndicator()); - ProjectIndexableFilesFilter files = new ProjectIndexableFilesFilter(filesSet, myFilesModCount); - project.putUserData(ourProjectFilesSetKey, new SoftReference(files)); - return files; - } - - @Nullable - private TIntHashSet collectFileIdsContainingAllKeys(@NotNull final ID indexId, - @NotNull final Collection dataKeys, - @NotNull final GlobalSearchScope filter, - @Nullable final Condition valueChecker, - @Nullable final ProjectIndexableFilesFilter projectFilesFilter) { - final ThrowableConvertor, TIntHashSet, StorageException> convertor = - new ThrowableConvertor, TIntHashSet, StorageException>() { - @Nullable - @Override - public TIntHashSet convert(@NotNull UpdatableIndex index) throws StorageException { - TIntHashSet mainIntersection = null; - - for (K dataKey : dataKeys) { - ProgressManager.checkCanceled(); - final TIntHashSet copy = new TIntHashSet(); - final ValueContainer container = index.getData(dataKey); - - for (final Iterator valueIt = container.getValueIterator(); valueIt.hasNext(); ) { - final V value = valueIt.next(); - if (valueChecker != null && !valueChecker.value(value)) { - continue; - } - - ValueContainer.IntIterator iterator = container.getInputIdsIterator(value); - - if (mainIntersection == null || iterator.size() < mainIntersection.size()) { - for (final ValueContainer.IntIterator inputIdsIterator = iterator; inputIdsIterator.hasNext(); ) { - final int id = inputIdsIterator.next(); - if (mainIntersection == null && (projectFilesFilter == null || projectFilesFilter.contains(id)) || - mainIntersection != null && mainIntersection.contains(id) - ) { - copy.add(id); - } - } - } else { - mainIntersection.forEach(new TIntProcedure() { - final ValueContainer.IntPredicate predicate = container.getValueAssociationPredicate(value); - @Override - public boolean execute(int id) { - if (predicate.contains(id)) copy.add(id); - return true; - } - }); - } - } - - mainIntersection = copy; - if (mainIntersection.isEmpty()) { - return new TIntHashSet(); - } - } - - return mainIntersection; - } - }; - - - return processExceptions(indexId, null, filter, convertor); - } - - private static boolean processVirtualFiles(@NotNull TIntHashSet ids, - @NotNull final GlobalSearchScope filter, - @NotNull final Processor processor) { - final PersistentFS fs = (PersistentFS)ManagingFS.getInstance(); - return ids.forEach(new TIntProcedure() { - @Override - public boolean execute(int id) { - ProgressManager.checkCanceled(); - VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id); - if (file != null && filter.accept(file)) { - return processor.process(file); - } - return true; - } - }); - } - - @Nullable - public static Throwable getCauseToRebuildIndex(@NotNull RuntimeException e) { - Throwable cause = e.getCause(); - if (cause instanceof StorageException || cause instanceof IOException || - cause instanceof IllegalArgumentException) return cause; - return null; - } - - public boolean getFilesWithKey(@NotNull final ID indexId, - @NotNull final Set dataKeys, - @NotNull Processor processor, - @NotNull GlobalSearchScope filter) { - try { - final UpdatableIndex index = getIndex(indexId); - if (index == null) { - return true; - } - final Project project = filter.getProject(); - //assert project != null : "GlobalSearchScope#getProject() should be not-null for all index queries"; - ensureUpToDate(indexId, project, filter); - - try { - index.getReadLock().lock(); - final List locals = new ArrayList(); - for (K dataKey : dataKeys) { - TIntHashSet local = new TIntHashSet(); - locals.add(local); - final ValueContainer container = index.getData(dataKey); - - for (final Iterator valueIt = container.getValueIterator(); valueIt.hasNext();) { - final V value = valueIt.next(); - for (final ValueContainer.IntIterator inputIdsIterator = container.getInputIdsIterator(value); inputIdsIterator.hasNext();) { - final int id = inputIdsIterator.next(); - local.add(id); - } - } - } - - if (locals.isEmpty()) { - return true; - } - - Collections.sort(locals, new Comparator() { - @Override - public int compare(TIntHashSet o1, TIntHashSet o2) { - return o1.size() - o2.size(); - } - }); - - final PersistentFS fs = (PersistentFS)ManagingFS.getInstance(); - TIntIterator ids = join(locals).iterator(); - ProjectIndexableFilesFilter projectIndexableFilesFilter = projectIndexableFiles(project); - while (ids.hasNext()) { - int id = ids.next(); - if (projectIndexableFilesFilter != null && !projectIndexableFilesFilter.contains(id)) continue; - //VirtualFile file = IndexInfrastructure.findFileById(fs, id); - VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id); - if (file != null && filter.accept(file)) { - if (!processor.process(file)) { - return false; - } - } - } - } - finally { - index.getReadLock().unlock(); - } - } - catch (StorageException e) { - scheduleRebuild(indexId, e); - } - catch (RuntimeException e) { - final Throwable cause = e.getCause(); - if (cause instanceof StorageException || cause instanceof IOException) { - scheduleRebuild(indexId, cause); - } - else { - throw e; - } - } - return true; - } - - @NotNull - private static TIntHashSet join(@NotNull List locals) { - TIntHashSet result = locals.get(0); - if (locals.size() > 1) { - TIntIterator it = result.iterator(); - - while (it.hasNext()) { - int id = it.next(); - for (int i = 1; i < locals.size(); i++) { - if (!locals.get(i).contains(id)) { - it.remove(); - break; - } - } - } - } - return result; - } - - public void scheduleRebuild(@NotNull final ID indexId, @NotNull final Throwable e) { - LOG.info(e); - requestRebuild(indexId); - try { - checkRebuild(indexId, false); - } - catch (ProcessCanceledException ignored) { - } - } - - private void checkRebuild(@NotNull final ID indexId, final boolean cleanupOnly) { - final AtomicInteger status = ourRebuildStatus.get(indexId); - if (status.get() == OK) return; - if (status.compareAndSet(REQUIRES_REBUILD, REBUILD_IN_PROGRESS)) { - cleanupProcessedFlag(); - - final Runnable rebuildRunnable = new Runnable() { - @Override - public void run() { - try { - clearIndex(indexId); - if (!cleanupOnly) { - scheduleIndexRebuild(false); - } - } - catch (StorageException e) { - requestRebuild(indexId); - LOG.info(e); - } - finally { - status.compareAndSet(REBUILD_IN_PROGRESS, OK); - } - } - }; - - if (cleanupOnly || myIsUnitTestMode) { - rebuildRunnable.run(); - } - else { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - new Task.Modal(null, "Updating index", false) { - @Override - public void run(@NotNull final ProgressIndicator indicator) { - indicator.setIndeterminate(true); - rebuildRunnable.run(); - } - }.queue(); - } - }); - } - } - - if (status.get() == REBUILD_IN_PROGRESS) { - throw new ProcessCanceledException(); - } - } - - private void scheduleIndexRebuild(boolean forceDumbMode) { - for (Project project : ProjectManager.getInstance().getOpenProjects()) { - final Set updatersToRun = Collections.singleton(new UnindexedFilesUpdater(project, this)); - final DumbServiceImpl service = DumbServiceImpl.getInstance(project); - if (forceDumbMode) { - service.queueCacheUpdateInDumbMode(updatersToRun); - } - else { - service.queueCacheUpdate(updatersToRun); - } - } - } - - private void clearIndex(@NotNull final ID indexId) throws StorageException { - final UpdatableIndex index = getIndex(indexId); - assert index != null: "Index with key " + indexId + " not found or not registered properly"; - index.clear(); - try { - IndexInfrastructure.rewriteVersion(IndexInfrastructure.getVersionFile(indexId), myIndexIdToVersionMap.get(indexId)); - } - catch (IOException e) { - LOG.error(e); - } - } - - @NotNull - private Set getUnsavedOrTransactedDocuments() { - final Set docs = new THashSet(Arrays.asList(myFileDocumentManager.getUnsavedDocuments())); - synchronized (myTransactionMap) { - docs.addAll(myTransactionMap.keySet()); - } - return docs; - } - - private void indexUnsavedDocuments(@NotNull ID indexId, - @Nullable Project project, - GlobalSearchScope filter, - VirtualFile restrictedFile) throws StorageException { - if (myUpToDateIndices.contains(indexId)) { - return; // no need to index unsaved docs - } - - final Set documents = getUnsavedOrTransactedDocuments(); - if (!documents.isEmpty()) { - // now index unsaved data - final StorageGuard.Holder guard = setDataBufferingEnabled(true); - try { - final Semaphore semaphore = myUnsavedDataIndexingSemaphores.get(indexId); - - assert semaphore != null : "Semaphore for unsaved data indexing was not initialized for index " + indexId; - - semaphore.down(); - boolean allDocsProcessed = true; - try { - for (Document document : documents) { - allDocsProcessed &= indexUnsavedDocument(document, indexId, project, filter, restrictedFile); - } - } - finally { - semaphore.up(); - - while (!semaphore.waitFor(500)) { // may need to wait until another thread is done with indexing - if (Thread.holdsLock(PsiLock.LOCK)) { - break; // hack. Most probably that other indexing threads is waiting for PsiLock, which we're are holding. - } - } - if (allDocsProcessed && !hasActiveTransactions()) { - myUpToDateIndices.add(indexId); // safe to set the flag here, because it will be cleared under the WriteAction - } - } - } - finally { - guard.leave(); - } - } - } - - private boolean hasActiveTransactions() { - synchronized (myTransactionMap) { - return !myTransactionMap.isEmpty(); - } - } - - private interface DocumentContent { - String getText(); - long getModificationStamp(); - } - - private static class AuthenticContent implements DocumentContent { - private final Document myDocument; - - private AuthenticContent(final Document document) { - myDocument = document; - } - - @Override - public String getText() { - return myDocument.getText(); - } - - @Override - public long getModificationStamp() { - return myDocument.getModificationStamp(); - } - } - - private static class PsiContent implements DocumentContent { - private final Document myDocument; - private final PsiFile myFile; - - private PsiContent(final Document document, final PsiFile file) { - myDocument = document; - myFile = file; - } - - @Override - public String getText() { - if (myFile.getModificationStamp() != myDocument.getModificationStamp()) { - final ASTNode node = myFile.getNode(); - assert node != null; - return node.getText(); - } - return myDocument.getText(); - } - - @Override - public long getModificationStamp() { - return myFile.getModificationStamp(); - } - } - -// returns false if doc was not indexed because the file does not fit in scope - private boolean indexUnsavedDocument(@NotNull final Document document, @NotNull final ID requestedIndexId, final Project project, - @Nullable GlobalSearchScope filter, @Nullable VirtualFile restrictedFile) throws StorageException { - final VirtualFile vFile = myFileDocumentManager.getFile(document); - if (!(vFile instanceof VirtualFileWithId) || !vFile.isValid()) { - return true; - } - - if (restrictedFile != null) { - if(vFile != restrictedFile) { - return false; - } - } - else if (filter != null && !filter.accept(vFile)) { - return false; - } - - final PsiFile dominantContentFile = findDominantPsiForDocument(document, project); - - final DocumentContent content; - if (dominantContentFile != null && dominantContentFile.getModificationStamp() != document.getModificationStamp()) { - content = new PsiContent(document, dominantContentFile); - } - else { - content = new AuthenticContent(document); - } - - final long currentDocStamp = content.getModificationStamp(); - if (currentDocStamp != myLastIndexedDocStamps.getAndSet(document, requestedIndexId, currentDocStamp)) { - final Ref exRef = new Ref(null); - ProgressManager.getInstance().executeNonCancelableSection(new Runnable() { - @Override - public void run() { - try { - final String contentText = content.getText(); - if (isTooLarge(vFile, contentText.length())) { - return; - } - - final FileContentImpl newFc = new FileContentImpl(vFile, contentText, vFile.getCharset()); - - if (dominantContentFile != null) { - dominantContentFile.putUserData(PsiFileImpl.BUILDING_STUB, true); - newFc.putUserData(IndexingDataKeys.PSI_FILE, dominantContentFile); - } - - if (content instanceof AuthenticContent) { - newFc.putUserData(EDITOR_HIGHLIGHTER, EditorHighlighterCache.getEditorHighlighterForCachesBuilding(document)); - } - - if (getInputFilter(requestedIndexId).acceptInput(vFile)) { - newFc.putUserData(IndexingDataKeys.PROJECT, project); - final int inputId = Math.abs(getFileId(vFile)); - getIndex(requestedIndexId).update(inputId, newFc); - } - - if (dominantContentFile != null) { - dominantContentFile.putUserData(PsiFileImpl.BUILDING_STUB, null); - } - } - catch (StorageException e) { - exRef.set(e); - } - } - }); - final StorageException storageException = exRef.get(); - if (storageException != null) { - throw storageException; - } - } - return true; - } - - public static final Key EDITOR_HIGHLIGHTER = new Key("Editor"); - - @Nullable - private PsiFile findDominantPsiForDocument(@NotNull Document document, @Nullable Project project) { - synchronized (myTransactionMap) { - PsiFile psiFile = myTransactionMap.get(document); - if (psiFile != null) return psiFile; - } - - return project == null ? null : findLatestKnownPsiForUncomittedDocument(document, project); - } - - private final StorageGuard myStorageLock = new StorageGuard(); - - @NotNull - private StorageGuard.Holder setDataBufferingEnabled(final boolean enabled) { - final StorageGuard.Holder holder = myStorageLock.enter(enabled); - for (ID indexId : myIndices.keySet()) { - final MapReduceIndex index = (MapReduceIndex)getIndex(indexId); - assert index != null; - final IndexStorage indexStorage = index.getStorage(); - ((MemoryIndexStorage)indexStorage).setBufferingEnabled(enabled); - } - return holder; - } - - private void cleanupMemoryStorage() { - myLastIndexedDocStamps.clear(); - for (ID indexId : myIndices.keySet()) { - final MapReduceIndex index = (MapReduceIndex)getIndex(indexId); - assert index != null; - final MemoryIndexStorage memStorage = (MemoryIndexStorage)index.getStorage(); - index.getWriteLock().lock(); - try { - memStorage.clearMemoryMap(); - } - finally { - index.getWriteLock().unlock(); - } - memStorage.fireMemoryStorageCleared(); - } - } - - private void dropUnregisteredIndices() { - final Set indicesToDrop = readRegisteredIndexNames(); - for (ID key : myIndices.keySet()) { - indicesToDrop.remove(key.toString()); - } - for (String s : indicesToDrop) { - FileUtil.delete(IndexInfrastructure.getIndexRootDir(ID.create(s))); - } - } - - public static void requestRebuild(ID indexId) { - requestRebuild(indexId, new Throwable()); - } - - public static void requestRebuild(ID indexId, Throwable throwable) { - cleanupProcessedFlag(); - LOG.info("Rebuild requested for index " + indexId, throwable); - ourRebuildStatus.get(indexId).set(REQUIRES_REBUILD); - } - - private UpdatableIndex getIndex(ID indexId) { - final Pair, InputFilter> pair = myIndices.get(indexId); - - assert pair != null : "Index data is absent for index " + indexId; - - //noinspection unchecked - return (UpdatableIndex)pair.getFirst(); - } - - private InputFilter getInputFilter(ID indexId) { - final Pair, InputFilter> pair = myIndices.get(indexId); - - assert pair != null : "Index data is absent for index " + indexId; - - return pair.getSecond(); - } - - public int getNumberOfPendingInvalidations() { - return myChangedFilesCollector.getNumberOfPendingInvalidations(); - } - - @NotNull - public Collection getFilesToUpdate(final Project project) { - return ContainerUtil.findAll(myChangedFilesCollector.getAllFilesToUpdate(), new Condition() { - @Override - public boolean value(VirtualFile virtualFile) { - for (IndexableFileSet set : myIndexableSets) { - final Project proj = myIndexableSetToProjectMap.get(set); - if (proj != null && !proj.equals(project)) { - continue; // skip this set as associated with a different project - } - if (set.isInSet(virtualFile)) { - return true; - } - } - return false; - } - }); - } - - public void processRefreshedFile(@NotNull Project project, @NotNull final com.intellij.ide.caches.FileContent fileContent) { - myChangedFilesCollector.ensureAllInvalidateTasksCompleted(); - myChangedFilesCollector.processFileImpl(project, fileContent, false); - } - - public void indexFileContent(@Nullable Project project, @NotNull com.intellij.ide.caches.FileContent content) { - myChangedFilesCollector.ensureAllInvalidateTasksCompleted(); - final VirtualFile file = content.getVirtualFile(); - FileContentImpl fc = null; - - PsiFile psiFile = null; - - FileTypeManagerImpl.cacheFileType(file, file.getFileType()); - try { - for (final ID indexId : myIndices.keySet()) { - if (shouldIndexFile(file, indexId)) { - if (fc == null) { - byte[] currentBytes; - try { - currentBytes = content.getBytes(); - } - catch (IOException e) { - currentBytes = ArrayUtil.EMPTY_BYTE_ARRAY; - } - fc = new FileContentImpl(file, currentBytes); - - psiFile = content.getUserData(IndexingDataKeys.PSI_FILE); - if (psiFile != null) { - psiFile.putUserData(PsiFileImpl.BUILDING_STUB, true); - fc.putUserData(IndexingDataKeys.PSI_FILE, psiFile); - } - if (project == null) { - project = ProjectUtil.guessProjectForFile(file); - } - fc.putUserData(IndexingDataKeys.PROJECT, project); - } - - try { - ProgressManager.checkCanceled(); - updateSingleIndex(indexId, file, fc); - } - catch (ProcessCanceledException e) { - myChangedFilesCollector.scheduleForUpdate(file); - throw e; - } - catch (StorageException e) { - requestRebuild(indexId); - LOG.info(e); - } - } - } - - if (psiFile != null) { - psiFile.putUserData(PsiFileImpl.BUILDING_STUB, null); - } - } finally { - FileTypeManagerImpl.cacheFileType(file, null); - } - } - - private void updateSingleIndex(final ID indexId, @NotNull final VirtualFile file, @Nullable final FileContent currentFC) - throws StorageException { - if (ourRebuildStatus.get(indexId).get() == REQUIRES_REBUILD) { - return; // the index is scheduled for rebuild, no need to update - } - myLocalModCount++; - - final StorageGuard.Holder lock = setDataBufferingEnabled(false); - - try { - final int inputId = Math.abs(getFileId(file)); - - final UpdatableIndex index = getIndex(indexId); - assert index != null; - - final Ref exRef = new Ref(null); - ProgressManager.getInstance().executeNonCancelableSection(new Runnable() { - @Override - public void run() { - try { - index.update(inputId, currentFC); - } - catch (StorageException e) { - exRef.set(e); - } - } - }); - final StorageException storageException = exRef.get(); - if (storageException != null) { - throw storageException; - } - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - if (file.isValid()) { - if (currentFC != null) { - IndexingStamp.update(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId)); - } - else { - // mark the file as unindexed - IndexingStamp.update(file, indexId, -1L); - } - } - } - }); - } - finally { - lock.leave(); - } + return ApplicationManager.getApplication().getComponent(FileBasedIndex.class); } public static int getFileId(@NotNull final VirtualFile file) { @@ -1693,653 +47,68 @@ public class FileBasedIndex implements ApplicationComponent { throw new IllegalArgumentException("Virtual file doesn't support id: " + file + ", implementation class: " + file.getClass().getName()); } - private boolean needsFileContentLoading(ID indexId) { - return !myNotRequiringContentIndices.contains(indexId); + public void requestRebuild(ID indexId) { + requestRebuild(indexId, new Throwable()); } - private abstract static class InvalidationTask implements Runnable { - private final VirtualFile mySubj; - protected InvalidationTask(final VirtualFile subj) { - mySubj = subj; - } - - public VirtualFile getSubj() { - return mySubj; - } - } - - private final class ChangedFilesCollector extends VirtualFileAdapter { - private final Set myFilesToUpdate = new ConcurrentHashSet(); - private final Queue myFutureInvalidations = new ConcurrentLinkedQueue(); - - private final ManagingFS myManagingFS = ManagingFS.getInstance(); - // No need to react on movement events since files stay valid, their ids don't change and all associated attributes remain intact. - - @Override - public void fileCreated(@NotNull final VirtualFileEvent event) { - markDirty(event, false); - } - - @Override - public void fileDeleted(@NotNull final VirtualFileEvent event) { - myFilesToUpdate.remove(event.getFile()); // no need to update it anymore - } - - @Override - public void fileCopied(@NotNull final VirtualFileCopyEvent event) { - markDirty(event, false); - } - - @Override - public void beforeFileDeletion(@NotNull final VirtualFileEvent event) { - invalidateIndices(event.getFile(), false); - } - - @Override - public void beforeContentsChange(@NotNull final VirtualFileEvent event) { - invalidateIndices(event.getFile(), true); - } - - @Override - public void contentsChanged(@NotNull final VirtualFileEvent event) { - markDirty(event, true); - } - - @Override - public void beforePropertyChange(@NotNull final VirtualFilePropertyEvent event) { - if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) { - // indexes may depend on file name - final VirtualFile file = event.getFile(); - if (!file.isDirectory()) { - // name change may lead to filetype change so the file might become not indexable - // in general case have to 'unindex' the file and index it again if needed after the name has been changed - invalidateIndices(file, false); - } - } - } - - @Override - public void propertyChanged(@NotNull final VirtualFilePropertyEvent event) { - if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) { - // indexes may depend on file name - if (!event.getFile().isDirectory()) { - markDirty(event, false); - } - } - } - - private void markDirty(@NotNull final VirtualFileEvent event, final boolean contentChange) { - final VirtualFile eventFile = event.getFile(); - cleanProcessedFlag(eventFile); - iterateIndexableFiles(eventFile, new Processor() { - @Override - public boolean process(@NotNull final VirtualFile file) { - if (!contentChange) ++myFilesModCount; - FileContent fileContent = null; - // handle 'content-less' indices separately - for (ID indexId : myNotRequiringContentIndices) { - if (getInputFilter(indexId).acceptInput(file)) { - try { - if (fileContent == null) { - fileContent = new FileContentImpl(file); - } - updateSingleIndex(indexId, file, fileContent); - } - catch (StorageException e) { - LOG.info(e); - requestRebuild(indexId); - } - } - } - // For 'normal indices' schedule the file for update and stop iteration if at least one index accepts it - if (!isTooLarge(file)) { - for (ID indexId : myIndices.keySet()) { - if (needsFileContentLoading(indexId) && getInputFilter(indexId).acceptInput(file)) { - scheduleForUpdate(file); - break; // no need to iterate further, as the file is already marked - } - } - } - - return true; - } - }); - IndexingStamp.flushCache(); - } - - public void scheduleForUpdate(VirtualFile file) { - myFilesToUpdate.add(file); - } - - void invalidateIndices(@NotNull final VirtualFile file, final boolean markForReindex) { - if (isUnderConfigOrSystem(file)) { - return; - } - if (file.isDirectory()) { - if (isMock(file) || myManagingFS.wereChildrenAccessed(file)) { - final Iterable children = file instanceof NewVirtualFile - ? ((NewVirtualFile)file).iterInDbChildren() : Arrays.asList(file.getChildren()); - for (VirtualFile child : children) { - invalidateIndices(child, markForReindex); - } - } - } - else { - cleanProcessedFlag(file); - IndexingStamp.flushCache(); - final List> affectedIndices = new ArrayList>(myIndices.size()); - - for (final ID indexId : myIndices.keySet()) { - try { - if (!needsFileContentLoading(indexId)) { - if (shouldUpdateIndex(file, indexId)) { - updateSingleIndex(indexId, file, null); - } - } - else { // the index requires file content - if (shouldUpdateIndex(file, indexId)) { - affectedIndices.add(indexId); - } - } - } - catch (StorageException e) { - LOG.info(e); - requestRebuild(indexId); - } - } - - if (!affectedIndices.isEmpty()) { - if (markForReindex && !isTooLarge(file)) { - // only mark the file as unindexed, reindex will be done lazily - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - for (ID indexId : affectedIndices) { - IndexingStamp.update(file, indexId, -2L); - } - } - }); - // the file is for sure not a dir and it was previously indexed by at least one index - scheduleForUpdate(file); - } - else { - myFutureInvalidations.offer(new InvalidationTask(file) { - @Override - public void run() { - removeFileDataFromIndices(affectedIndices, file); - } - }); - } - } - if (!markForReindex) { - final boolean removedFromUpdateQueue = myFilesToUpdate.remove(file);// no need to update it anymore - if (removedFromUpdateQueue && affectedIndices.isEmpty()) { - // Currently the file is about to be deleted and previously it was scheduled for update and not processed up to now. - // Because the file was scheduled for update, at the moment of scheduling it was marked as unindexed, - // so, to be on the safe side, we have to schedule data invalidation from all content-requiring indices for this file - myFutureInvalidations.offer(new InvalidationTask(file) { - @Override - public void run() { - removeFileDataFromIndices(myRequiringContentIndices, file); - } - }); - } - } - - IndexingStamp.flushCache(); - } - } - - private void removeFileDataFromIndices(@NotNull Collection> affectedIndices, @NotNull VirtualFile file) { - Throwable unexpectedError = null; - for (ID indexId : affectedIndices) { - try { - updateSingleIndex(indexId, file, null); - } - catch (StorageException e) { - LOG.info(e); - requestRebuild(indexId); - } - catch (ProcessCanceledException ignored) { - } - catch (Throwable e) { - LOG.info(e); - if (unexpectedError == null) { - unexpectedError = e; - } - } - } - IndexingStamp.flushCache(); - if (unexpectedError != null) { - LOG.error(unexpectedError); - } - } - - public int getNumberOfPendingInvalidations() { - return myFutureInvalidations.size(); - } - - public void ensureAllInvalidateTasksCompleted() { - final int size = getNumberOfPendingInvalidations(); - if (size == 0) { - return; - } - final ProgressIndicator current = ProgressManager.getInstance().getProgressIndicator(); - final ProgressIndicator indicator = current != null ? current : new EmptyProgressIndicator(); - indicator.setText(""); - int count = 0; - while (true) { - InvalidationTask task = myFutureInvalidations.poll(); - - if (task == null) { - break; - } - indicator.setFraction((double)count++ /size); - indicator.setText2(task.getSubj().getPresentableUrl()); - task.run(); - } - } - - private void iterateIndexableFiles(@NotNull final VirtualFile file, @NotNull final Processor processor) { - if (file.isDirectory()) { - final ContentIterator iterator = new ContentIterator() { - @Override - public boolean processFile(@NotNull final VirtualFile fileOrDir) { - if (!fileOrDir.isDirectory()) { - processor.process(fileOrDir); - } - return true; - } - }; - - for (IndexableFileSet set : myIndexableSets) { - if (set.isInSet(file)) { - set.iterateIndexableFilesIn(file, iterator); - } - } - } - else { - for (IndexableFileSet set : myIndexableSets) { - if (set.isInSet(file)) { - processor.process(file); - break; - } - } - } - } - - public Collection getAllFilesToUpdate() { - if (myFilesToUpdate.isEmpty()) { - return Collections.emptyList(); - } - return new ArrayList(myFilesToUpdate); - } - - private final Semaphore myForceUpdateSemaphore = new Semaphore(); - - private void forceUpdate(@Nullable Project project, @Nullable GlobalSearchScope filter, @Nullable VirtualFile restrictedTo, - boolean onlyRemoveOutdatedData) { - myChangedFilesCollector.ensureAllInvalidateTasksCompleted(); - for (VirtualFile file: getAllFilesToUpdate()) { - if (filter == null || filter.accept(file) || file == restrictedTo) { - try { - myForceUpdateSemaphore.down(); - // process only files that can affect result - processFileImpl(project, new com.intellij.ide.caches.FileContent(file), onlyRemoveOutdatedData); - } - finally { - myForceUpdateSemaphore.up(); - } - } - } - - // If several threads entered the method at the same time and there were files to update, - // all the threads should leave the method synchronously after all the files scheduled for update are reindexed, - // no matter which thread will do reindexing job. - // Thus we ensure that all the threads that entered the method will get the most recent data - - while (!myForceUpdateSemaphore.waitFor(500)) { // may need to wait until another thread is done with indexing - if (Thread.holdsLock(PsiLock.LOCK)) { - break; // hack. Most probably that other indexing threads is waiting for PsiLock, which we're are holding. - } - } - } - - private void processFileImpl(Project project, @NotNull final com.intellij.ide.caches.FileContent fileContent, boolean onlyRemoveOutdatedData) { - final VirtualFile file = fileContent.getVirtualFile(); - final boolean reallyRemoved = myFilesToUpdate.remove(file); - if (reallyRemoved && file.isValid()) { - if (onlyRemoveOutdatedData) { - // on shutdown there is no need to re-index the file, just remove outdated data from indices - final List> affected = new ArrayList>(); - for (final ID indexId : myIndices.keySet()) { - if (getInputFilter(indexId).acceptInput(file)) { - affected.add(indexId); - } - } - removeFileDataFromIndices(affected, file); - } - else { - indexFileContent(project, fileContent); - } - IndexingStamp.flushCache(); - } - } - } - - private class UnindexedFilesFinder implements CollectingContentIterator { - private final List myFiles = new ArrayList(); - private final ProgressIndicator myProgressIndicator; - - private UnindexedFilesFinder() { - myProgressIndicator = ProgressManager.getInstance().getProgressIndicator(); - } - - @NotNull - @Override - public List getFiles() { - return myFiles; - } - - @Override - public boolean processFile(@NotNull final VirtualFile file) { - if (!file.isValid()) { - return true; - } - if (!file.isDirectory()) { - if (file instanceof NewVirtualFile && ((NewVirtualFile)file).getFlag(ALREADY_PROCESSED)) { - return true; - } - - if (file instanceof VirtualFileWithId) { - try { - FileTypeManagerImpl.cacheFileType(file, file.getFileType()); - - boolean oldStuff = true; - if (!isTooLarge(file)) { - for (ID indexId : myIndices.keySet()) { - try { - if (needsFileContentLoading(indexId) && shouldIndexFile(file, indexId)) { - myFiles.add(file); - oldStuff = false; - break; - } - } - catch (RuntimeException e) { - final Throwable cause = e.getCause(); - if (cause instanceof IOException || cause instanceof StorageException) { - LOG.info(e); - requestRebuild(indexId); - } - else { - throw e; - } - } - } - } - FileContent fileContent = null; - for (ID indexId : myNotRequiringContentIndices) { - if (shouldIndexFile(file, indexId)) { - oldStuff = false; - try { - if (fileContent == null) { - fileContent = new FileContentImpl(file); - } - updateSingleIndex(indexId, file, fileContent); - } - catch (StorageException e) { - LOG.info(e); - requestRebuild(indexId); - } - } - } - IndexingStamp.flushCache(); - - if (oldStuff && file instanceof NewVirtualFile) { - ((NewVirtualFile)file).setFlag(ALREADY_PROCESSED, true); - } - } - finally { - FileTypeManagerImpl.cacheFileType(file, null); - } - } - } - else { - if (myProgressIndicator != null) { - myProgressIndicator.setText("Scanning files to index"); - myProgressIndicator.setText2(file.getPresentableUrl()); - } - } - return true; - } - } - - private boolean shouldUpdateIndex(final VirtualFile file, final ID indexId) { - return getInputFilter(indexId).acceptInput(file) && - (isMock(file) || IndexingStamp.isFileIndexed(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId))); - } - - private boolean shouldIndexFile(final VirtualFile file, final ID indexId) { - return getInputFilter(indexId).acceptInput(file) && - (isMock(file) || !IndexingStamp.isFileIndexed(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId))); - } - - private boolean isUnderConfigOrSystem(@NotNull VirtualFile file) { - final String filePath = file.getPath(); - return myConfigPath != null && FileUtil.startsWith(filePath, myConfigPath) || - mySystemPath != null && FileUtil.startsWith(filePath, mySystemPath); - } - - private static boolean isMock(final VirtualFile file) { - return !(file instanceof NewVirtualFile); - } - - private boolean isTooLarge(@NotNull VirtualFile file) { - if (SingleRootFileViewProvider.isTooLarge(file)) { - final FileType type = file.getFileType(); - return !myNoLimitCheckTypes.contains(type); - } - return false; - } - - private boolean isTooLarge(@NotNull VirtualFile file, long contentSize) { - if (SingleRootFileViewProvider.isTooLarge(file, contentSize)) { - final FileType type = file.getFileType(); - return !myNoLimitCheckTypes.contains(type); - } - return false; + @NonNls + @NotNull + public String getComponentName() { + return "FileBasedIndex"; } @NotNull - public CollectingContentIterator createContentIterator() { - ++myFilesModCount; - return new UnindexedFilesFinder(); - } + public abstract List getValues(@NotNull ID indexId, @NotNull K dataKey, @NotNull GlobalSearchScope filter); - public void registerIndexableSet(@NotNull IndexableFileSet set, @Nullable Project project) { - myIndexableSets.add(set); - myIndexableSetToProjectMap.put(set, project); - } + @NotNull + public abstract Collection getContainingFiles(@NotNull ID indexId, + @NotNull K dataKey, + @NotNull GlobalSearchScope filter); - public void removeIndexableSet(@NotNull IndexableFileSet set) { - myChangedFilesCollector.forceUpdate(null, null, null, true); - myIndexableSets.remove(set); - myIndexableSetToProjectMap.remove(set); - } + public abstract boolean processValues(@NotNull ID indexId, + @NotNull K dataKey, + @Nullable VirtualFile inFile, + @NotNull FileBasedIndex.ValueProcessor processor, + @NotNull GlobalSearchScope filter); - @Nullable - private static PsiFile findLatestKnownPsiForUncomittedDocument(@NotNull Document doc, @NotNull Project project) { - return PsiDocumentManager.getInstance(project).getCachedPsiFile(doc); - } - - private static class IndexableFilesFilter implements InputFilter { - private final InputFilter myDelegate; + public abstract boolean processFilesContainingAllKeys(@NotNull ID indexId, + @NotNull Collection dataKeys, + @NotNull GlobalSearchScope filter, + @Nullable Condition valueChecker, + @NotNull Processor processor); - private IndexableFilesFilter(InputFilter delegate) { - myDelegate = delegate; - } + @NotNull + public abstract Collection getAllKeys(@NotNull ID indexId, @NotNull Project project); - @Override - public boolean acceptInput(final VirtualFile file) { - return file instanceof VirtualFileWithId && myDelegate.acceptInput(file); - } - } + public abstract void ensureUpToDate(@NotNull ID indexId, @Nullable Project project, @Nullable GlobalSearchScope filter); - private static void cleanupProcessedFlag() { - final VirtualFile[] roots = ManagingFS.getInstance().getRoots(); - for (VirtualFile root : roots) { - cleanProcessedFlag(root); - } - } + protected abstract void ensureUpToDate(@NotNull ID indexId, + @Nullable Project project, + @Nullable GlobalSearchScope filter, + @Nullable VirtualFile restrictedFile); - private static void cleanProcessedFlag(@NotNull final VirtualFile file) { - if (!(file instanceof NewVirtualFile)) return; - - final NewVirtualFile nvf = (NewVirtualFile)file; - if (file.isDirectory()) { - for (VirtualFile child : nvf.getCachedChildren()) { - cleanProcessedFlag(child); - } - } - else { - nvf.setFlag(ALREADY_PROCESSED, false); - } - } + public abstract void requestRebuild(ID indexId, Throwable throwable); - public static void iterateIndexableFiles(@NotNull final ContentIterator processor, @NotNull Project project, ProgressIndicator indicator) { - if (project.isDisposed()) { - return; - } - final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex(); - // iterate project content - projectFileIndex.iterateContent(processor); + public abstract void scheduleRebuild(@NotNull ID indexId, @NotNull Throwable e); - if (project.isDisposed()) { - return; - } + public abstract void requestReindex(@NotNull VirtualFile file); - Set visitedRoots = new THashSet(); - for (IndexedRootsProvider provider : Extensions.getExtensions(IndexedRootsProvider.EP_NAME)) { - //important not to depend on project here, to support per-project background reindex - // each client gives a project to FileBasedIndex - if (project.isDisposed()) { - return; - } - for (VirtualFile root : IndexableSetContributor.getRootsToIndex(provider)) { - if (visitedRoots.add(root)) { - iterateRecursively(root, processor, indicator); - } - } - for (VirtualFile root : IndexableSetContributor.getProjectRootsToIndex(provider, project)) { - if (visitedRoots.add(root)) { - iterateRecursively(root, processor, indicator); - } - } - } + public abstract void requestReindexExcluded(@NotNull VirtualFile file); - if (project.isDisposed()) { - return; - } - // iterate associated libraries - for (Module module : ModuleManager.getInstance(project).getModules()) { - if (module.isDisposed()) { - return; - } - OrderEntry[] orderEntries = ModuleRootManager.getInstance(module).getOrderEntries(); - for (OrderEntry orderEntry : orderEntries) { - if (orderEntry instanceof LibraryOrderEntry || orderEntry instanceof JdkOrderEntry) { - if (orderEntry.isValid()) { - final VirtualFile[] libSources = orderEntry.getFiles(OrderRootType.SOURCES); - final VirtualFile[] libClasses = orderEntry.getFiles(OrderRootType.CLASSES); - for (VirtualFile[] roots : new VirtualFile[][]{libSources, libClasses}) { - for (VirtualFile root : roots) { - if (visitedRoots.add(root)) { - iterateRecursively(root, processor, indicator); - } - } - } - } - } - } - } - } + public abstract boolean getFilesWithKey(@NotNull ID indexId, + @NotNull Set dataKeys, + @NotNull Processor processor, + @NotNull GlobalSearchScope filter); - private static void iterateRecursively(@Nullable final VirtualFile root, @NotNull final ContentIterator processor, @Nullable ProgressIndicator indicator) { - if (root != null) { - if (indicator != null) { - indicator.checkCanceled(); - indicator.setText2(root.getPresentableUrl()); - } - - if (root.isDirectory()) { - for (VirtualFile file : root.getChildren()) { - if (file.isDirectory()) { - iterateRecursively(file, processor, indicator); - } - else { - processor.processFile(file); - } - } - } - else { - processor.processFile(root); - } - } - } - - private static class StorageGuard { - private int myHolds = 0; - - public interface Holder { - void leave(); - } - - private final Holder myTrueHolder = new Holder() { - @Override - public void leave() { - StorageGuard.this.leave(true); - } - }; - private final Holder myFalseHolder = new Holder() { - @Override - public void leave() { - StorageGuard.this.leave(false); - } - }; - - @NotNull - public synchronized Holder enter(boolean mode) { - if (mode) { - while (myHolds < 0) { - try { - wait(); - } - catch (InterruptedException ignored) { - } - } - myHolds++; - return myTrueHolder; - } - else { - while (myHolds > 0) { - try { - wait(); - } - catch (InterruptedException ignored) { - } - } - myHolds--; - return myFalseHolder; - } - } - - private synchronized void leave(boolean mode) { - myHolds += mode? -1 : 1; - if (myHolds == 0) { - notifyAll(); - } - } + public abstract boolean processAllKeys(@NotNull ID indexId, Processor processor, @Nullable Project project); + public interface ValueProcessor { + /** + * @param value a value to process + * @param file the file the value came from + * @return false if no further processing is needed, true otherwise + */ + boolean process(VirtualFile file, V value); } } diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java new file mode 100644 index 000000000000..1cf3b2677d89 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java @@ -0,0 +1,2327 @@ +/* + * 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.util.indexing; + +import com.intellij.AppTopics; +import com.intellij.history.LocalHistory; +import com.intellij.ide.caches.CacheUpdater; +import com.intellij.lang.ASTNode; +import com.intellij.notification.NotificationDisplayType; +import com.intellij.notification.NotificationGroup; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.application.ApplicationAdapter; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.highlighter.EditorHighlighter; +import com.intellij.openapi.editor.impl.EditorHighlighterCache; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter; +import com.intellij.openapi.fileTypes.*; +import com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.progress.*; +import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator; +import com.intellij.openapi.project.*; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.vfs.*; +import com.intellij.openapi.vfs.ex.VirtualFileManagerEx; +import com.intellij.openapi.vfs.newvfs.BulkFileListener; +import com.intellij.openapi.vfs.newvfs.ManagingFS; +import com.intellij.openapi.vfs.newvfs.NewVirtualFile; +import com.intellij.openapi.vfs.newvfs.events.VFileEvent; +import com.intellij.openapi.vfs.newvfs.persistent.FlushingDaemon; +import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; +import com.intellij.psi.*; +import com.intellij.psi.impl.PsiDocumentTransactionListener; +import com.intellij.psi.impl.source.PsiFileImpl; +import com.intellij.psi.search.EverythingGlobalScope; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.stubs.SerializationManager; +import com.intellij.util.*; +import com.intellij.util.concurrency.Semaphore; +import com.intellij.util.containers.ConcurrentHashSet; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.io.*; +import com.intellij.util.io.DataOutputStream; +import com.intellij.util.io.storage.HeavyProcessLatch; +import com.intellij.util.messages.MessageBus; +import com.intellij.util.messages.MessageBusConnection; +import gnu.trove.*; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.io.*; +import java.lang.ref.SoftReference; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author Eugene Zhuravlev + * Date: Dec 20, 2007 + */ + +public class FileBasedIndexImpl extends FileBasedIndex { + private static final Logger LOG = Logger.getInstance("#com.intellij.util.indexing.FileBasedIndexImpl"); + @NonNls + private static final String CORRUPTION_MARKER_NAME = "corruption.marker"; + private final Map, Pair, InputFilter>> myIndices = new THashMap, Pair, InputFilter>>(); + private final Map, Semaphore> myUnsavedDataIndexingSemaphores = new THashMap, Semaphore>(); + private final TObjectIntHashMap> myIndexIdToVersionMap = new TObjectIntHashMap>(); + private final Set> myNotRequiringContentIndices = new THashSet>(); + private final Set> myRequiringContentIndices = new THashSet>(); + private final Set myNoLimitCheckTypes = new THashSet(); + + private final PerIndexDocumentVersionMap myLastIndexedDocStamps = new PerIndexDocumentVersionMap(); + @NotNull private final ChangedFilesCollector myChangedFilesCollector; + + private final List myIndexableSets = ContainerUtil.createEmptyCOWList(); + private final Map myIndexableSetToProjectMap = new THashMap(); + + private static final int OK = 1; + private static final int REQUIRES_REBUILD = 2; + private static final int REBUILD_IN_PROGRESS = 3; + private static final Map, AtomicInteger> ourRebuildStatus = new THashMap, AtomicInteger>(); + + private final VirtualFileManagerEx myVfManager; + private final FileDocumentManager myFileDocumentManager; + private final FileTypeManager myFileTypeManager; + private final ConcurrentHashSet> myUpToDateIndices = new ConcurrentHashSet>(); + private final Map myTransactionMap = new THashMap(); + + private static final int ALREADY_PROCESSED = 0x04; + + @Nullable private final String myConfigPath; + @Nullable private final String mySystemPath; + private final boolean myIsUnitTestMode; + @Nullable private ScheduledFuture myFlushingFuture; + private volatile int myLocalModCount; + private volatile int myFilesModCount; + + @Override + public void requestReindex(@NotNull final VirtualFile file) { + myChangedFilesCollector.invalidateIndices(file, true); + } + + @Override + public void requestReindexExcluded(@NotNull final VirtualFile file) { + myChangedFilesCollector.invalidateIndices(file, false); + } + + public FileBasedIndexImpl(final VirtualFileManagerEx vfManager, + FileDocumentManager fdm, + FileTypeManager fileTypeManager, + @NotNull MessageBus bus, + SerializationManager sm + /*need this parameter to ensure component dependency*/) throws IOException { + myVfManager = vfManager; + myFileDocumentManager = fdm; + myFileTypeManager = fileTypeManager; + myIsUnitTestMode = ApplicationManager.getApplication().isUnitTestMode(); + myConfigPath = calcConfigPath(PathManager.getConfigPath()); + mySystemPath = calcConfigPath(PathManager.getSystemPath()); + + final MessageBusConnection connection = bus.connect(); + connection.subscribe(PsiDocumentTransactionListener.TOPIC, new PsiDocumentTransactionListener() { + @Override + public void transactionStarted(final Document doc, final PsiFile file) { + if (file != null) { + synchronized (myTransactionMap) { + myTransactionMap.put(doc, file); + } + myUpToDateIndices.clear(); + } + } + + @Override + public void transactionCompleted(final Document doc, final PsiFile file) { + synchronized (myTransactionMap) { + myTransactionMap.remove(doc); + } + } + }); + + connection.subscribe(FileTypeManager.TOPIC, new FileTypeListener() { + @Nullable private Map> myTypeToExtensionMap; + @Override + public void beforeFileTypesChanged(final FileTypeEvent event) { + cleanupProcessedFlag(); + myTypeToExtensionMap = new THashMap>(); + for (FileType type : myFileTypeManager.getRegisteredFileTypes()) { + myTypeToExtensionMap.put(type, getExtensions(type)); + } + } + + @Override + public void fileTypesChanged(final FileTypeEvent event) { + final Map> oldExtensions = myTypeToExtensionMap; + myTypeToExtensionMap = null; + if (oldExtensions != null) { + final Map> newExtensions = new THashMap>(); + for (FileType type : myFileTypeManager.getRegisteredFileTypes()) { + newExtensions.put(type, getExtensions(type)); + } + // we are interested only in extension changes or removals. + // addition of an extension is handled separately by RootsChanged event + if (!newExtensions.keySet().containsAll(oldExtensions.keySet())) { + rebuildAllIndices(); + return; + } + for (Map.Entry> entry : oldExtensions.entrySet()) { + FileType fileType = entry.getKey(); + Set strings = entry.getValue(); + if (!newExtensions.get(fileType).containsAll(strings)) { + rebuildAllIndices(); + return; + } + } + } + } + + @NotNull + private Set getExtensions(@NotNull FileType type) { + final Set set = new THashSet(); + for (FileNameMatcher matcher : myFileTypeManager.getAssociations(type)) { + set.add(matcher.getPresentableString()); + } + return set; + } + + private void rebuildAllIndices() { + for (ID indexId : myIndices.keySet()) { + try { + clearIndex(indexId); + } + catch (StorageException e) { + LOG.info(e); + } + } + scheduleIndexRebuild(true); + } + }); + + connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() { + @Override + public void before(@NotNull List events) { + for (VFileEvent event : events) { + final Object requestor = event.getRequestor(); + if (requestor instanceof FileDocumentManager || requestor instanceof PsiManager || requestor == LocalHistory.VFS_EVENT_REQUESTOR) { + cleanupMemoryStorage(); + break; + } + } + } + + @Override + public void after(@NotNull List events) { + } + }); + + connection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerAdapter() { + @Override + public void fileContentReloaded(VirtualFile file, @NotNull Document document) { + cleanupMemoryStorage(); + } + + @Override + public void unsavedDocumentsDropped() { + cleanupMemoryStorage(); + } + }); + + ApplicationManager.getApplication().addApplicationListener(new ApplicationAdapter() { + @Override + public void writeActionStarted(Object action) { + myUpToDateIndices.clear(); + } + }); + + myChangedFilesCollector = new ChangedFilesCollector(); + + /* + final File workInProgressFile = getMarkerFile(); + if (workInProgressFile.exists()) { + // previous IDEA session was closed incorrectly, so drop all indices + FileUtil.delete(PathManager.getIndexRoot()); + } + */ + + try { + final FileBasedIndexExtension[] extensions = Extensions.getExtensions(FileBasedIndexExtension.EXTENSION_POINT_NAME); + for (FileBasedIndexExtension extension : extensions) { + ourRebuildStatus.put(extension.getName(), new AtomicInteger(OK)); + } + + final File corruptionMarker = new File(PathManager.getIndexRoot(), CORRUPTION_MARKER_NAME); + final boolean currentVersionCorrupted = corruptionMarker.exists(); + boolean versionChanged = false; + for (FileBasedIndexExtension extension : extensions) { + versionChanged |= registerIndexer(extension, currentVersionCorrupted); + } + FileUtil.delete(corruptionMarker); + + String rebuildNotification = null; + if (currentVersionCorrupted) { + rebuildNotification = "Index files on disk are corrupted. Indices will be rebuilt."; + } + else if (versionChanged) { + rebuildNotification = "Index file format has changed for some indices. These indices will be rebuilt."; + } + if (rebuildNotification != null + && !ApplicationManager.getApplication().isHeadlessEnvironment() + && Registry.is("ide.showIndexRebuildMessage")) { + new NotificationGroup("Indexing", NotificationDisplayType.BALLOON, false) + .createNotification("Index Rebuild", rebuildNotification, NotificationType.INFORMATION, null).notify(null); + } + + dropUnregisteredIndices(); + + // check if rebuild was requested for any index during registration + for (ID indexId : myIndices.keySet()) { + if (ourRebuildStatus.get(indexId).compareAndSet(REQUIRES_REBUILD, OK)) { + try { + clearIndex(indexId); + } + catch (StorageException e) { + requestRebuild(indexId); + LOG.error(e); + } + } + } + + myVfManager.addVirtualFileListener(myChangedFilesCollector); + + registerIndexableSet(new AdditionalIndexableFileSet(), null); + } + finally { + ShutDownTracker.getInstance().registerShutdownTask(new Runnable() { + @Override + public void run() { + performShutdown(); + } + }); + //FileUtil.createIfDoesntExist(workInProgressFile); + saveRegisteredIndices(myIndices.keySet()); + myFlushingFuture = FlushingDaemon.everyFiveSeconds(new Runnable() { + int lastModCount = 0; + @Override + public void run() { + if (lastModCount == myLocalModCount) { + flushAllIndices(lastModCount); + } + lastModCount = myLocalModCount; + } + }); + + } + } + + @Override + public void initComponent() { + } + + @Nullable + private static String calcConfigPath(final String path) { + try { + final String _path = FileUtil.toSystemIndependentName(new File(path).getCanonicalPath()); + return _path.endsWith("/")? _path : _path + "/" ; + } + catch (IOException e) { + LOG.info(e); + return null; + } + } + + /** + * @return true if registered index requires full rebuild for some reason, e.g. is just created or corrupted + * + * @param extension + * @param isCurrentVersionCorrupted + */ + private boolean registerIndexer(@NotNull final FileBasedIndexExtension extension, final boolean isCurrentVersionCorrupted) throws IOException { + final ID name = extension.getName(); + final int version = extension.getVersion(); + final File versionFile = IndexInfrastructure.getVersionFile(name); + final boolean versionFileExisted = versionFile.exists(); + boolean versionChanged = false; + if (isCurrentVersionCorrupted || IndexInfrastructure.versionDiffers(versionFile, version)) { + if (!isCurrentVersionCorrupted && versionFileExisted) { + versionChanged = true; + LOG.info("Version has changed for index " + name + ". The index will be rebuilt."); + } + FileUtil.delete(IndexInfrastructure.getIndexRootDir(name)); + IndexInfrastructure.rewriteVersion(versionFile, version); + } + + for (int attempt = 0; attempt < 2; attempt++) { + try { + final MapIndexStorage storage = new MapIndexStorage(IndexInfrastructure.getStorageFile(name), extension.getKeyDescriptor(), extension.getValueExternalizer(), extension.getCacheSize()); + final MemoryIndexStorage memStorage = new MemoryIndexStorage(storage); + final UpdatableIndex index = createIndex(name, extension, memStorage); + final InputFilter inputFilter = extension.getInputFilter(); + + assert inputFilter != null : "Index extension " + name + " must provide non-null input filter"; + + myIndices.put(name, new Pair, InputFilter>(index, new IndexableFilesFilter(inputFilter))); + myUnsavedDataIndexingSemaphores.put(name, new Semaphore()); + myIndexIdToVersionMap.put(name, version); + if (!extension.dependsOnFileContent()) { + myNotRequiringContentIndices.add(name); + } + else { + myRequiringContentIndices.add(name); + } + myNoLimitCheckTypes.addAll(extension.getFileTypesWithSizeLimitNotApplicable()); + break; + } + catch (IOException e) { + LOG.info(e); + FileUtil.delete(IndexInfrastructure.getIndexRootDir(name)); + IndexInfrastructure.rewriteVersion(versionFile, version); + } + } + return versionChanged; + } + + private static void saveRegisteredIndices(@NotNull Collection> ids) { + final File file = getRegisteredIndicesFile(); + try { + FileUtil.createIfDoesntExist(file); + final DataOutputStream os = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); + try { + os.writeInt(ids.size()); + for (ID id : ids) { + IOUtil.writeString(id.toString(), os); + } + } + finally { + os.close(); + } + } + catch (IOException ignored) { + } + } + + @NotNull + private static Set readRegisteredIndexNames() { + final Set result = new THashSet(); + try { + final DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(getRegisteredIndicesFile()))); + try { + final int size = in.readInt(); + for (int idx = 0; idx < size; idx++) { + result.add(IOUtil.readString(in)); + } + } + finally { + in.close(); + } + } + catch (IOException ignored) { + } + return result; + } + + @NotNull + private static File getRegisteredIndicesFile() { + return new File(PathManager.getIndexRoot(), "registered"); + } + + @NotNull + private UpdatableIndex createIndex(@NotNull final ID indexId, @NotNull final FileBasedIndexExtension extension, @NotNull final MemoryIndexStorage storage) throws IOException { + final MapReduceIndex index; + if (extension instanceof CustomImplementationFileBasedIndexExtension) { + final UpdatableIndex custom = ((CustomImplementationFileBasedIndexExtension)extension).createIndexImplementation(indexId, this, storage); + + assert custom != null : "Custom index implementation must not be null; index: " + indexId; + + if (!(custom instanceof MapReduceIndex)) { + return custom; + } + index = (MapReduceIndex)custom; + } + else { + index = new MapReduceIndex(indexId, extension.getIndexer(), storage); + } + + final KeyDescriptor keyDescriptor = extension.getKeyDescriptor(); + index.setInputIdToDataKeysIndex(new Factory>>() { + @Override + public PersistentHashMap> create() { + try { + return createIdToDataKeysIndex(indexId, keyDescriptor, storage); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + + return index; + } + + @NotNull + private static PersistentHashMap> createIdToDataKeysIndex(@NotNull final ID indexId, + @NotNull final KeyDescriptor keyDescriptor, + @NotNull MemoryIndexStorage storage) throws IOException { + final File indexStorageFile = IndexInfrastructure.getInputIndexStorageFile(indexId); + final Ref isBufferingMode = new Ref(false); + final TIntObjectHashMap> tempMap = new TIntObjectHashMap>(); + + final DataExternalizer> dataExternalizer = new DataExternalizer>() { + @Override + public void save(DataOutput out, @NotNull Collection value) throws IOException { + try { + DataInputOutputUtil.writeINT(out, value.size()); + for (K key : value) { + keyDescriptor.save(out, key); + } + } + catch (IllegalArgumentException e) { + throw new IOException("Error saving data for index " + indexId, e); + } + } + + @NotNull + @Override + public Collection read(DataInput in) throws IOException { + try { + final int size = DataInputOutputUtil.readINT(in); + final List list = new ArrayList(size); + for (int idx = 0; idx < size; idx++) { + list.add(keyDescriptor.read(in)); + } + return list; + } + catch (IllegalArgumentException e) { + throw new IOException("Error reading data for index " + indexId, e); + } + } + }; + + // Important! Update IdToDataKeysIndex depending on the sate of "buffering" flag from the MemoryStorage. + // If buffering is on, all changes should be done in memory (similar to the way it is done in memory storage). + // Otherwise data in IdToDataKeysIndex will not be in sync with the 'main' data in the index on disk and index updates will be based on the + // wrong sets of keys for the given file. This will lead to unpredictable results in main index because it will not be + // cleared properly before updating (removed data will still be present on disk). See IDEA-52223 for illustration of possible effects. + + final PersistentHashMap> map = new PersistentHashMap>( + indexStorageFile, EnumeratorIntegerDescriptor.INSTANCE, dataExternalizer + ) { + + @Override + protected Collection doGet(Integer integer) throws IOException { + if (isBufferingMode.get()) { + final Collection collection = tempMap.get(integer); + if (collection != null) { + return collection; + } + } + return super.doGet(integer); + } + + @Override + protected void doPut(Integer integer, @Nullable Collection ks) throws IOException { + if (isBufferingMode.get()) { + tempMap.put(integer, ks == null? Collections.emptySet() : ks); + } + else { + super.doPut(integer, ks); + } + } + + @Override + protected void doRemove(Integer integer) throws IOException { + if (isBufferingMode.get()) { + tempMap.put(integer, Collections.emptySet()); + } + else { + super.doRemove(integer); + } + } + }; + + storage.addBufferingStateListsner(new MemoryIndexStorage.BufferingStateListener() { + @Override + public void bufferingStateChanged(boolean newState) { + synchronized (map) { + isBufferingMode.set(newState); + } + } + @Override + public void memoryStorageCleared() { + synchronized (map) { + tempMap.clear(); + } + } + }); + return map; + } + + @Override + public void disposeComponent() { + performShutdown(); + } + + private final AtomicBoolean myShutdownPerformed = new AtomicBoolean(false); + + private void performShutdown() { + if (!myShutdownPerformed.compareAndSet(false, true)) { + return; // already shut down + } + try { + if (myFlushingFuture != null) { + myFlushingFuture.cancel(false); + myFlushingFuture = null; + } + + myFileDocumentManager.saveAllDocuments(); + } + finally { + LOG.info("START INDEX SHUTDOWN"); + try { + myChangedFilesCollector.forceUpdate(null, null, null, true); + + for (ID indexId : myIndices.keySet()) { + final UpdatableIndex index = getIndex(indexId); + assert index != null; + checkRebuild(indexId, true); // if the index was scheduled for rebuild, only clean it + //LOG.info("DISPOSING " + indexId); + index.dispose(); + } + + myVfManager.removeVirtualFileListener(myChangedFilesCollector); + + //FileUtil.delete(getMarkerFile()); + } + catch (Throwable e) { + LOG.info("Problems during index shutdown", e); + throw new RuntimeException(e); + } + LOG.info("END INDEX SHUTDOWN"); + } + } + + private void flushAllIndices(final long modCount) { + if (HeavyProcessLatch.INSTANCE.isRunning()) { + return; + } + IndexingStamp.flushCache(); + for (ID indexId : new ArrayList>(myIndices.keySet())) { + if (HeavyProcessLatch.INSTANCE.isRunning() || modCount != myLocalModCount) { + return; // do not interfere with 'main' jobs + } + try { + final UpdatableIndex index = getIndex(indexId); + if (index != null) { + index.flush(); + } + } + catch (StorageException e) { + LOG.info(e); + requestRebuild(indexId); + } + } + + if (!HeavyProcessLatch.INSTANCE.isRunning() && modCount == myLocalModCount) { // do not interfere with 'main' jobs + SerializationManager.getInstance().flushNameStorage(); + } + } + + /** + * @param project it is guaranteed to return data which is up-to-date withing the project + * Keys obtained from the files which do not belong to the project specified may not be up-to-date or even exist + */ + @Override + @NotNull + public Collection getAllKeys(@NotNull final ID indexId, @NotNull Project project) { + Set allKeys = new THashSet(); + processAllKeys(indexId, new CommonProcessors.CollectProcessor(allKeys), project); + return allKeys; + } + + /** + * @param project it is guaranteed to return data which is up-to-date withing the project + * Keys obtained from the files which do not belong to the project specified may not be up-to-date or even exist + */ + @Override + public boolean processAllKeys(@NotNull final ID indexId, Processor processor, @Nullable Project project) { + try { + final UpdatableIndex index = getIndex(indexId); + if (index == null) { + return true; + } + ensureUpToDate(indexId, project, project != null? GlobalSearchScope.allScope(project) : new EverythingGlobalScope()); + return index.processAllKeys(processor); + } + catch (StorageException e) { + scheduleRebuild(indexId, e); + } + catch (RuntimeException e) { + final Throwable cause = e.getCause(); + if (cause instanceof StorageException || cause instanceof IOException) { + scheduleRebuild(indexId, cause); + } + else { + throw e; + } + } + + return false; + } + + private static final ThreadLocal myUpToDateCheckState = new ThreadLocal(); + + public static void disableUpToDateCheckForCurrentThread() { + final Integer currentValue = myUpToDateCheckState.get(); + myUpToDateCheckState.set(currentValue == null? 1 : currentValue.intValue() + 1); + } + + public static void enableUpToDateCheckForCurrentThread() { + final Integer currentValue = myUpToDateCheckState.get(); + if (currentValue != null) { + final int newValue = currentValue.intValue() - 1; + if (newValue != 0) { + myUpToDateCheckState.set(newValue); + } + else { + myUpToDateCheckState.remove(); + } + } + } + + private static boolean isUpToDateCheckEnabled() { + final Integer value = myUpToDateCheckState.get(); + return value == null || value.intValue() == 0; + } + + + private final ThreadLocal myReentrancyGuard = new ThreadLocal() { + @Override + protected Boolean initialValue() { + return Boolean.FALSE; + } + }; + + /** + * DO NOT CALL DIRECTLY IN CLIENT CODE + * The method is internal to indexing engine end is called internally. The method is public due to implementation details + */ + @Override + public void ensureUpToDate(@NotNull final ID indexId, @Nullable Project project, @Nullable GlobalSearchScope filter) { + ensureUpToDate(indexId, project, filter, null); + } + + @Override + protected void ensureUpToDate(@NotNull final ID indexId, + @Nullable Project project, + @Nullable GlobalSearchScope filter, + @Nullable VirtualFile restrictedFile) { + if (!needsFileContentLoading(indexId)) { + return; //indexed eagerly in foreground while building unindexed file list + } + if (isDumb(project)) { + handleDumbMode(project); + } + + if (myReentrancyGuard.get().booleanValue()) { + //assert false : "ensureUpToDate() is not reentrant!"; + return; + } + myReentrancyGuard.set(Boolean.TRUE); + + try { + myChangedFilesCollector.ensureAllInvalidateTasksCompleted(); + if (isUpToDateCheckEnabled()) { + try { + checkRebuild(indexId, false); + myChangedFilesCollector.forceUpdate(project, filter, restrictedFile, false); + indexUnsavedDocuments(indexId, project, filter, restrictedFile); + } + catch (StorageException e) { + scheduleRebuild(indexId, e); + } + catch (RuntimeException e) { + final Throwable cause = e.getCause(); + if (cause instanceof StorageException || cause instanceof IOException) { + scheduleRebuild(indexId, e); + } + else { + throw e; + } + } + } + } + finally { + myReentrancyGuard.set(Boolean.FALSE); + } + } + + private static void handleDumbMode(@Nullable Project project) { + ProgressManager.checkCanceled(); // DumbModeAction.CANCEL + + if (project != null) { + final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); + if (progressIndicator instanceof BackgroundableProcessIndicator) { + final BackgroundableProcessIndicator indicator = (BackgroundableProcessIndicator)progressIndicator; + if (indicator.getDumbModeAction() == DumbModeAction.WAIT) { + assert !ApplicationManager.getApplication().isDispatchThread(); + DumbService.getInstance(project).waitForSmartMode(); + return; + } + } + } + + throw new IndexNotReadyException(); + } + + private static boolean isDumb(@Nullable Project project) { + if (project != null) { + return DumbServiceImpl.getInstance(project).isDumb(); + } + for (Project proj : ProjectManager.getInstance().getOpenProjects()) { + if (DumbServiceImpl.getInstance(proj).isDumb()) { + return true; + } + } + return false; + } + + @Override + @NotNull + public List getValues(@NotNull final ID indexId, @NotNull K dataKey, @NotNull final GlobalSearchScope filter) { + final List values = new SmartList(); + processValuesImpl(indexId, dataKey, true, null, new ValueProcessor() { + @Override + public boolean process(final VirtualFile file, final V value) { + values.add(value); + return true; + } + }, filter); + return values; + } + + @Override + @NotNull + public Collection getContainingFiles(@NotNull final ID indexId, @NotNull K dataKey, @NotNull final GlobalSearchScope filter) { + final Set files = new THashSet(); + processValuesImpl(indexId, dataKey, false, null, new ValueProcessor() { + @Override + public boolean process(final VirtualFile file, final V value) { + files.add(file); + return true; + } + }, filter); + return files; + } + + + /** + * @return false if ValueProcessor.process() returned false; true otherwise or if ValueProcessor was not called at all + */ + @Override + public boolean processValues(@NotNull final ID indexId, @NotNull final K dataKey, @Nullable final VirtualFile inFile, + @NotNull ValueProcessor processor, @NotNull final GlobalSearchScope filter) { + return processValuesImpl(indexId, dataKey, false, inFile, processor, filter); + } + + + + + @Nullable + private R processExceptions(@NotNull final ID indexId, + @Nullable final VirtualFile restrictToFile, + @NotNull final GlobalSearchScope filter, + @NotNull ThrowableConvertor, R, StorageException> computable) { + try { + final UpdatableIndex index = getIndex(indexId); + if (index == null) { + return null; + } + final Project project = filter.getProject(); + //assert project != null : "GlobalSearchScope#getProject() should be not-null for all index queries"; + ensureUpToDate(indexId, project, filter, restrictToFile); + + try { + index.getReadLock().lock(); + return computable.convert(index); + } + finally { + index.getReadLock().unlock(); + } + } + catch (StorageException e) { + scheduleRebuild(indexId, e); + } + catch (RuntimeException e) { + final Throwable cause = getCauseToRebuildIndex(e); + if (cause != null) { + scheduleRebuild(indexId, cause); + } + else { + throw e; + } + } + return null; + } + + private boolean processValuesImpl(@NotNull final ID indexId, final K dataKey, final boolean ensureValueProcessedOnce, + @Nullable final VirtualFile restrictToFile, @NotNull final ValueProcessor processor, + @NotNull final GlobalSearchScope filter) { + ThrowableConvertor, Boolean, StorageException> keyProcessor = new ThrowableConvertor, Boolean, StorageException>() { + @Override + public Boolean convert(@NotNull UpdatableIndex index) throws StorageException { + final ValueContainer container = index.getData(dataKey); + + boolean shouldContinue = true; + + if (restrictToFile != null) { + if (restrictToFile instanceof VirtualFileWithId) { + final int restrictedFileId = getFileId(restrictToFile); + for (final Iterator valueIt = container.getValueIterator(); valueIt.hasNext(); ) { + final V value = valueIt.next(); + if (container.isAssociated(value, restrictedFileId)) { + shouldContinue = processor.process(restrictToFile, value); + if (!shouldContinue) { + break; + } + } + } + } + } + else { + final PersistentFS fs = (PersistentFS)ManagingFS.getInstance(); + ProjectIndexableFilesFilter projectFilesSet = projectIndexableFiles(filter.getProject()); + VALUES_LOOP: for (final Iterator valueIt = container.getValueIterator(); valueIt.hasNext();) { + final V value = valueIt.next(); + for (final ValueContainer.IntIterator inputIdsIterator = container.getInputIdsIterator(value); inputIdsIterator.hasNext();) { + final int id = inputIdsIterator.next(); + if (projectFilesSet != null && !projectFilesSet.contains(id)) continue; + VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id); + if (file != null && filter.accept(file)) { + shouldContinue = processor.process(file, value); + if (!shouldContinue) { + break VALUES_LOOP; + } + if (ensureValueProcessedOnce) { + break; // continue with the next value + } + } + } + } + } + return shouldContinue; + } + }; + final Boolean result = processExceptions(indexId, restrictToFile, filter, keyProcessor); + return result == null || result.booleanValue(); + } + + @Override + public boolean processFilesContainingAllKeys(@NotNull final ID indexId, + @NotNull final Collection dataKeys, + @NotNull final GlobalSearchScope filter, + @Nullable Condition valueChecker, + @NotNull final Processor processor) { + ProjectIndexableFilesFilter filesSet = projectIndexableFiles(filter.getProject()); + final TIntHashSet set = collectFileIdsContainingAllKeys(indexId, dataKeys, filter, valueChecker, filesSet); + return set != null && processVirtualFiles(set, filter, processor); + } + + private static final Key> ourProjectFilesSetKey = Key.create("projectFiles"); + + public static final class ProjectIndexableFilesFilter { + private static final int SHIFT = 6; + private static final int MASK = (1 << SHIFT) - 1; + private final long[] myBitMask; + private final int myModificationCount; + private final int myMinId; + private final int myMaxId; + + private ProjectIndexableFilesFilter(@NotNull TIntHashSet set, int modificationCount) { + myModificationCount = modificationCount; + final int[] minMax = new int[2]; + if (set.size() > 0) { + minMax[0] = minMax[1] = set.iterator().next(); + } + set.forEach(new TIntProcedure() { + @Override + public boolean execute(int value) { + minMax[0] = Math.min(minMax[0], value); + minMax[1] = Math.max(minMax[1], value); + return true; + } + }); + myMaxId = minMax[1]; + myMinId = minMax[0]; + myBitMask = new long[((myMaxId - myMinId) >> SHIFT) + 1]; + set.forEach(new TIntProcedure() { + @Override + public boolean execute(int value) { + value = value - myMinId; + myBitMask[value >> SHIFT] |= (1L << (value & MASK)); + return true; + } + }); + } + + public boolean contains(int id) { + if (id < myMinId) return false; + if (id > myMaxId) return false; + id -= myMinId; + return (myBitMask[id >> SHIFT] & (1L << (id & MASK))) != 0; + } + } + + @Nullable + public ProjectIndexableFilesFilter projectIndexableFiles(@Nullable Project project) { + if (project == null) return null; + + SoftReference reference = project.getUserData(ourProjectFilesSetKey); + ProjectIndexableFilesFilter data = reference != null ? reference.get() : null; + if (data != null && data.myModificationCount == myFilesModCount) return data; + + final TIntHashSet filesSet = new TIntHashSet(); + iterateIndexableFiles(new ContentIterator() { + @Override + public boolean processFile(@NotNull VirtualFile fileOrDir) { + filesSet.add(((VirtualFileWithId)fileOrDir).getId()); + return true; + } + }, project, ProgressManager.getInstance().getProgressIndicator()); + ProjectIndexableFilesFilter files = new ProjectIndexableFilesFilter(filesSet, myFilesModCount); + project.putUserData(ourProjectFilesSetKey, new SoftReference(files)); + return files; + } + + @Nullable + private TIntHashSet collectFileIdsContainingAllKeys(@NotNull final ID indexId, + @NotNull final Collection dataKeys, + @NotNull final GlobalSearchScope filter, + @Nullable final Condition valueChecker, + @Nullable final ProjectIndexableFilesFilter projectFilesFilter) { + final ThrowableConvertor, TIntHashSet, StorageException> convertor = + new ThrowableConvertor, TIntHashSet, StorageException>() { + @Nullable + @Override + public TIntHashSet convert(@NotNull UpdatableIndex index) throws StorageException { + TIntHashSet mainIntersection = null; + + for (K dataKey : dataKeys) { + ProgressManager.checkCanceled(); + final TIntHashSet copy = new TIntHashSet(); + final ValueContainer container = index.getData(dataKey); + + for (final Iterator valueIt = container.getValueIterator(); valueIt.hasNext(); ) { + final V value = valueIt.next(); + if (valueChecker != null && !valueChecker.value(value)) { + continue; + } + + ValueContainer.IntIterator iterator = container.getInputIdsIterator(value); + + if (mainIntersection == null || iterator.size() < mainIntersection.size()) { + for (final ValueContainer.IntIterator inputIdsIterator = iterator; inputIdsIterator.hasNext(); ) { + final int id = inputIdsIterator.next(); + if (mainIntersection == null && (projectFilesFilter == null || projectFilesFilter.contains(id)) || + mainIntersection != null && mainIntersection.contains(id) + ) { + copy.add(id); + } + } + } else { + mainIntersection.forEach(new TIntProcedure() { + final ValueContainer.IntPredicate predicate = container.getValueAssociationPredicate(value); + @Override + public boolean execute(int id) { + if (predicate.contains(id)) copy.add(id); + return true; + } + }); + } + } + + mainIntersection = copy; + if (mainIntersection.isEmpty()) { + return new TIntHashSet(); + } + } + + return mainIntersection; + } + }; + + + return processExceptions(indexId, null, filter, convertor); + } + + private static boolean processVirtualFiles(@NotNull TIntHashSet ids, + @NotNull final GlobalSearchScope filter, + @NotNull final Processor processor) { + final PersistentFS fs = (PersistentFS)ManagingFS.getInstance(); + return ids.forEach(new TIntProcedure() { + @Override + public boolean execute(int id) { + ProgressManager.checkCanceled(); + VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id); + if (file != null && filter.accept(file)) { + return processor.process(file); + } + return true; + } + }); + } + + @Nullable + public static Throwable getCauseToRebuildIndex(@NotNull RuntimeException e) { + Throwable cause = e.getCause(); + if (cause instanceof StorageException || cause instanceof IOException || + cause instanceof IllegalArgumentException) return cause; + return null; + } + + @Override + public boolean getFilesWithKey(@NotNull final ID indexId, + @NotNull final Set dataKeys, + @NotNull Processor processor, + @NotNull GlobalSearchScope filter) { + try { + final UpdatableIndex index = getIndex(indexId); + if (index == null) { + return true; + } + final Project project = filter.getProject(); + //assert project != null : "GlobalSearchScope#getProject() should be not-null for all index queries"; + ensureUpToDate(indexId, project, filter); + + try { + index.getReadLock().lock(); + final List locals = new ArrayList(); + for (K dataKey : dataKeys) { + TIntHashSet local = new TIntHashSet(); + locals.add(local); + final ValueContainer container = index.getData(dataKey); + + for (final Iterator valueIt = container.getValueIterator(); valueIt.hasNext();) { + final V value = valueIt.next(); + for (final ValueContainer.IntIterator inputIdsIterator = container.getInputIdsIterator(value); inputIdsIterator.hasNext();) { + final int id = inputIdsIterator.next(); + local.add(id); + } + } + } + + if (locals.isEmpty()) { + return true; + } + + Collections.sort(locals, new Comparator() { + @Override + public int compare(TIntHashSet o1, TIntHashSet o2) { + return o1.size() - o2.size(); + } + }); + + final PersistentFS fs = (PersistentFS)ManagingFS.getInstance(); + TIntIterator ids = join(locals).iterator(); + ProjectIndexableFilesFilter projectIndexableFilesFilter = projectIndexableFiles(project); + while (ids.hasNext()) { + int id = ids.next(); + if (projectIndexableFilesFilter != null && !projectIndexableFilesFilter.contains(id)) continue; + //VirtualFile file = IndexInfrastructure.findFileById(fs, id); + VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id); + if (file != null && filter.accept(file)) { + if (!processor.process(file)) { + return false; + } + } + } + } + finally { + index.getReadLock().unlock(); + } + } + catch (StorageException e) { + scheduleRebuild(indexId, e); + } + catch (RuntimeException e) { + final Throwable cause = e.getCause(); + if (cause instanceof StorageException || cause instanceof IOException) { + scheduleRebuild(indexId, cause); + } + else { + throw e; + } + } + return true; + } + + @NotNull + private static TIntHashSet join(@NotNull List locals) { + TIntHashSet result = locals.get(0); + if (locals.size() > 1) { + TIntIterator it = result.iterator(); + + while (it.hasNext()) { + int id = it.next(); + for (int i = 1; i < locals.size(); i++) { + if (!locals.get(i).contains(id)) { + it.remove(); + break; + } + } + } + } + return result; + } + + @Override + public void scheduleRebuild(@NotNull final ID indexId, @NotNull final Throwable e) { + LOG.info(e); + requestRebuild(indexId); + try { + checkRebuild(indexId, false); + } + catch (ProcessCanceledException ignored) { + } + } + + private void checkRebuild(@NotNull final ID indexId, final boolean cleanupOnly) { + final AtomicInteger status = ourRebuildStatus.get(indexId); + if (status.get() == OK) return; + if (status.compareAndSet(REQUIRES_REBUILD, REBUILD_IN_PROGRESS)) { + cleanupProcessedFlag(); + + final Runnable rebuildRunnable = new Runnable() { + @Override + public void run() { + try { + clearIndex(indexId); + if (!cleanupOnly) { + scheduleIndexRebuild(false); + } + } + catch (StorageException e) { + requestRebuild(indexId); + LOG.info(e); + } + finally { + status.compareAndSet(REBUILD_IN_PROGRESS, OK); + } + } + }; + + if (cleanupOnly || myIsUnitTestMode) { + rebuildRunnable.run(); + } + else { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + new Task.Modal(null, "Updating index", false) { + @Override + public void run(@NotNull final ProgressIndicator indicator) { + indicator.setIndeterminate(true); + rebuildRunnable.run(); + } + }.queue(); + } + }); + } + } + + if (status.get() == REBUILD_IN_PROGRESS) { + throw new ProcessCanceledException(); + } + } + + private void scheduleIndexRebuild(boolean forceDumbMode) { + for (Project project : ProjectManager.getInstance().getOpenProjects()) { + final Set updatersToRun = Collections.singleton(new UnindexedFilesUpdater(project, this)); + final DumbServiceImpl service = DumbServiceImpl.getInstance(project); + if (forceDumbMode) { + service.queueCacheUpdateInDumbMode(updatersToRun); + } + else { + service.queueCacheUpdate(updatersToRun); + } + } + } + + private void clearIndex(@NotNull final ID indexId) throws StorageException { + final UpdatableIndex index = getIndex(indexId); + assert index != null: "Index with key " + indexId + " not found or not registered properly"; + index.clear(); + try { + IndexInfrastructure.rewriteVersion(IndexInfrastructure.getVersionFile(indexId), myIndexIdToVersionMap.get(indexId)); + } + catch (IOException e) { + LOG.error(e); + } + } + + @NotNull + private Set getUnsavedOrTransactedDocuments() { + final Set docs = new THashSet(Arrays.asList(myFileDocumentManager.getUnsavedDocuments())); + synchronized (myTransactionMap) { + docs.addAll(myTransactionMap.keySet()); + } + return docs; + } + + private void indexUnsavedDocuments(@NotNull ID indexId, + @Nullable Project project, + GlobalSearchScope filter, + VirtualFile restrictedFile) throws StorageException { + if (myUpToDateIndices.contains(indexId)) { + return; // no need to index unsaved docs + } + + final Set documents = getUnsavedOrTransactedDocuments(); + if (!documents.isEmpty()) { + // now index unsaved data + final StorageGuard.Holder guard = setDataBufferingEnabled(true); + try { + final Semaphore semaphore = myUnsavedDataIndexingSemaphores.get(indexId); + + assert semaphore != null : "Semaphore for unsaved data indexing was not initialized for index " + indexId; + + semaphore.down(); + boolean allDocsProcessed = true; + try { + for (Document document : documents) { + allDocsProcessed &= indexUnsavedDocument(document, indexId, project, filter, restrictedFile); + } + } + finally { + semaphore.up(); + + while (!semaphore.waitFor(500)) { // may need to wait until another thread is done with indexing + if (Thread.holdsLock(PsiLock.LOCK)) { + break; // hack. Most probably that other indexing threads is waiting for PsiLock, which we're are holding. + } + } + if (allDocsProcessed && !hasActiveTransactions()) { + myUpToDateIndices.add(indexId); // safe to set the flag here, because it will be cleared under the WriteAction + } + } + } + finally { + guard.leave(); + } + } + } + + private boolean hasActiveTransactions() { + synchronized (myTransactionMap) { + return !myTransactionMap.isEmpty(); + } + } + + private interface DocumentContent { + String getText(); + long getModificationStamp(); + } + + private static class AuthenticContent implements DocumentContent { + private final Document myDocument; + + private AuthenticContent(final Document document) { + myDocument = document; + } + + @Override + public String getText() { + return myDocument.getText(); + } + + @Override + public long getModificationStamp() { + return myDocument.getModificationStamp(); + } + } + + private static class PsiContent implements DocumentContent { + private final Document myDocument; + private final PsiFile myFile; + + private PsiContent(final Document document, final PsiFile file) { + myDocument = document; + myFile = file; + } + + @Override + public String getText() { + if (myFile.getModificationStamp() != myDocument.getModificationStamp()) { + final ASTNode node = myFile.getNode(); + assert node != null; + return node.getText(); + } + return myDocument.getText(); + } + + @Override + public long getModificationStamp() { + return myFile.getModificationStamp(); + } + } + +// returns false if doc was not indexed because the file does not fit in scope + private boolean indexUnsavedDocument(@NotNull final Document document, @NotNull final ID requestedIndexId, final Project project, + @Nullable GlobalSearchScope filter, @Nullable VirtualFile restrictedFile) throws StorageException { + final VirtualFile vFile = myFileDocumentManager.getFile(document); + if (!(vFile instanceof VirtualFileWithId) || !vFile.isValid()) { + return true; + } + + if (restrictedFile != null) { + if(vFile != restrictedFile) { + return false; + } + } + else if (filter != null && !filter.accept(vFile)) { + return false; + } + + final PsiFile dominantContentFile = findDominantPsiForDocument(document, project); + + final DocumentContent content; + if (dominantContentFile != null && dominantContentFile.getModificationStamp() != document.getModificationStamp()) { + content = new PsiContent(document, dominantContentFile); + } + else { + content = new AuthenticContent(document); + } + + final long currentDocStamp = content.getModificationStamp(); + if (currentDocStamp != myLastIndexedDocStamps.getAndSet(document, requestedIndexId, currentDocStamp)) { + final Ref exRef = new Ref(null); + ProgressManager.getInstance().executeNonCancelableSection(new Runnable() { + @Override + public void run() { + try { + final String contentText = content.getText(); + if (isTooLarge(vFile, contentText.length())) { + return; + } + + final FileContentImpl newFc = new FileContentImpl(vFile, contentText, vFile.getCharset()); + + if (dominantContentFile != null) { + dominantContentFile.putUserData(PsiFileImpl.BUILDING_STUB, true); + newFc.putUserData(IndexingDataKeys.PSI_FILE, dominantContentFile); + } + + if (content instanceof AuthenticContent) { + newFc.putUserData(EDITOR_HIGHLIGHTER, EditorHighlighterCache.getEditorHighlighterForCachesBuilding(document)); + } + + if (getInputFilter(requestedIndexId).acceptInput(vFile)) { + newFc.putUserData(IndexingDataKeys.PROJECT, project); + final int inputId = Math.abs(getFileId(vFile)); + getIndex(requestedIndexId).update(inputId, newFc); + } + + if (dominantContentFile != null) { + dominantContentFile.putUserData(PsiFileImpl.BUILDING_STUB, null); + } + } + catch (StorageException e) { + exRef.set(e); + } + } + }); + final StorageException storageException = exRef.get(); + if (storageException != null) { + throw storageException; + } + } + return true; + } + + public static final Key EDITOR_HIGHLIGHTER = new Key("Editor"); + + @Nullable + private PsiFile findDominantPsiForDocument(@NotNull Document document, @Nullable Project project) { + synchronized (myTransactionMap) { + PsiFile psiFile = myTransactionMap.get(document); + if (psiFile != null) return psiFile; + } + + return project == null ? null : findLatestKnownPsiForUncomittedDocument(document, project); + } + + private final StorageGuard myStorageLock = new StorageGuard(); + + @NotNull + private StorageGuard.Holder setDataBufferingEnabled(final boolean enabled) { + final StorageGuard.Holder holder = myStorageLock.enter(enabled); + for (ID indexId : myIndices.keySet()) { + final MapReduceIndex index = (MapReduceIndex)getIndex(indexId); + assert index != null; + final IndexStorage indexStorage = index.getStorage(); + ((MemoryIndexStorage)indexStorage).setBufferingEnabled(enabled); + } + return holder; + } + + private void cleanupMemoryStorage() { + myLastIndexedDocStamps.clear(); + for (ID indexId : myIndices.keySet()) { + final MapReduceIndex index = (MapReduceIndex)getIndex(indexId); + assert index != null; + final MemoryIndexStorage memStorage = (MemoryIndexStorage)index.getStorage(); + index.getWriteLock().lock(); + try { + memStorage.clearMemoryMap(); + } + finally { + index.getWriteLock().unlock(); + } + memStorage.fireMemoryStorageCleared(); + } + } + + private void dropUnregisteredIndices() { + final Set indicesToDrop = readRegisteredIndexNames(); + for (ID key : myIndices.keySet()) { + indicesToDrop.remove(key.toString()); + } + for (String s : indicesToDrop) { + FileUtil.delete(IndexInfrastructure.getIndexRootDir(ID.create(s))); + } + } + + @Override + public void requestRebuild(ID indexId, Throwable throwable) { + cleanupProcessedFlag(); + LOG.info("Rebuild requested for index " + indexId, throwable); + ourRebuildStatus.get(indexId).set(REQUIRES_REBUILD); + } + + private UpdatableIndex getIndex(ID indexId) { + final Pair, InputFilter> pair = myIndices.get(indexId); + + assert pair != null : "Index data is absent for index " + indexId; + + //noinspection unchecked + return (UpdatableIndex)pair.getFirst(); + } + + private InputFilter getInputFilter(ID indexId) { + final Pair, InputFilter> pair = myIndices.get(indexId); + + assert pair != null : "Index data is absent for index " + indexId; + + return pair.getSecond(); + } + + public int getNumberOfPendingInvalidations() { + return myChangedFilesCollector.getNumberOfPendingInvalidations(); + } + + @NotNull + public Collection getFilesToUpdate(final Project project) { + return ContainerUtil.findAll(myChangedFilesCollector.getAllFilesToUpdate(), new Condition() { + @Override + public boolean value(VirtualFile virtualFile) { + for (IndexableFileSet set : myIndexableSets) { + final Project proj = myIndexableSetToProjectMap.get(set); + if (proj != null && !proj.equals(project)) { + continue; // skip this set as associated with a different project + } + if (set.isInSet(virtualFile)) { + return true; + } + } + return false; + } + }); + } + + public void processRefreshedFile(@NotNull Project project, @NotNull final com.intellij.ide.caches.FileContent fileContent) { + myChangedFilesCollector.ensureAllInvalidateTasksCompleted(); + myChangedFilesCollector.processFileImpl(project, fileContent, false); + } + + public void indexFileContent(@Nullable Project project, @NotNull com.intellij.ide.caches.FileContent content) { + myChangedFilesCollector.ensureAllInvalidateTasksCompleted(); + final VirtualFile file = content.getVirtualFile(); + FileContentImpl fc = null; + + PsiFile psiFile = null; + + FileTypeManagerImpl.cacheFileType(file, file.getFileType()); + try { + for (final ID indexId : myIndices.keySet()) { + if (shouldIndexFile(file, indexId)) { + if (fc == null) { + byte[] currentBytes; + try { + currentBytes = content.getBytes(); + } + catch (IOException e) { + currentBytes = ArrayUtil.EMPTY_BYTE_ARRAY; + } + fc = new FileContentImpl(file, currentBytes); + + psiFile = content.getUserData(IndexingDataKeys.PSI_FILE); + if (psiFile != null) { + psiFile.putUserData(PsiFileImpl.BUILDING_STUB, true); + fc.putUserData(IndexingDataKeys.PSI_FILE, psiFile); + } + if (project == null) { + project = ProjectUtil.guessProjectForFile(file); + } + fc.putUserData(IndexingDataKeys.PROJECT, project); + } + + try { + ProgressManager.checkCanceled(); + updateSingleIndex(indexId, file, fc); + } + catch (ProcessCanceledException e) { + myChangedFilesCollector.scheduleForUpdate(file); + throw e; + } + catch (StorageException e) { + requestRebuild(indexId); + LOG.info(e); + } + } + } + + if (psiFile != null) { + psiFile.putUserData(PsiFileImpl.BUILDING_STUB, null); + } + } finally { + FileTypeManagerImpl.cacheFileType(file, null); + } + } + + private void updateSingleIndex(final ID indexId, @NotNull final VirtualFile file, @Nullable final FileContent currentFC) + throws StorageException { + if (ourRebuildStatus.get(indexId).get() == REQUIRES_REBUILD) { + return; // the index is scheduled for rebuild, no need to update + } + myLocalModCount++; + + final StorageGuard.Holder lock = setDataBufferingEnabled(false); + + try { + final int inputId = Math.abs(getFileId(file)); + + final UpdatableIndex index = getIndex(indexId); + assert index != null; + + final Ref exRef = new Ref(null); + ProgressManager.getInstance().executeNonCancelableSection(new Runnable() { + @Override + public void run() { + try { + index.update(inputId, currentFC); + } + catch (StorageException e) { + exRef.set(e); + } + } + }); + final StorageException storageException = exRef.get(); + if (storageException != null) { + throw storageException; + } + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + if (file.isValid()) { + if (currentFC != null) { + IndexingStamp.update(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId)); + } + else { + // mark the file as unindexed + IndexingStamp.update(file, indexId, -1L); + } + } + } + }); + } + finally { + lock.leave(); + } + } + + private boolean needsFileContentLoading(ID indexId) { + return !myNotRequiringContentIndices.contains(indexId); + } + + private abstract static class InvalidationTask implements Runnable { + private final VirtualFile mySubj; + + protected InvalidationTask(final VirtualFile subj) { + mySubj = subj; + } + + public VirtualFile getSubj() { + return mySubj; + } + } + + private final class ChangedFilesCollector extends VirtualFileAdapter { + private final Set myFilesToUpdate = new ConcurrentHashSet(); + private final Queue myFutureInvalidations = new ConcurrentLinkedQueue(); + + private final ManagingFS myManagingFS = ManagingFS.getInstance(); + // No need to react on movement events since files stay valid, their ids don't change and all associated attributes remain intact. + + @Override + public void fileCreated(@NotNull final VirtualFileEvent event) { + markDirty(event, false); + } + + @Override + public void fileDeleted(@NotNull final VirtualFileEvent event) { + myFilesToUpdate.remove(event.getFile()); // no need to update it anymore + } + + @Override + public void fileCopied(@NotNull final VirtualFileCopyEvent event) { + markDirty(event, false); + } + + @Override + public void beforeFileDeletion(@NotNull final VirtualFileEvent event) { + invalidateIndices(event.getFile(), false); + } + + @Override + public void beforeContentsChange(@NotNull final VirtualFileEvent event) { + invalidateIndices(event.getFile(), true); + } + + @Override + public void contentsChanged(@NotNull final VirtualFileEvent event) { + markDirty(event, true); + } + + @Override + public void beforePropertyChange(@NotNull final VirtualFilePropertyEvent event) { + if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) { + // indexes may depend on file name + final VirtualFile file = event.getFile(); + if (!file.isDirectory()) { + // name change may lead to filetype change so the file might become not indexable + // in general case have to 'unindex' the file and index it again if needed after the name has been changed + invalidateIndices(file, false); + } + } + } + + @Override + public void propertyChanged(@NotNull final VirtualFilePropertyEvent event) { + if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) { + // indexes may depend on file name + if (!event.getFile().isDirectory()) { + markDirty(event, false); + } + } + } + + private void markDirty(@NotNull final VirtualFileEvent event, final boolean contentChange) { + final VirtualFile eventFile = event.getFile(); + cleanProcessedFlag(eventFile); + iterateIndexableFiles(eventFile, new Processor() { + @Override + public boolean process(@NotNull final VirtualFile file) { + if (!contentChange) ++myFilesModCount; + FileContent fileContent = null; + // handle 'content-less' indices separately + for (ID indexId : myNotRequiringContentIndices) { + if (getInputFilter(indexId).acceptInput(file)) { + try { + if (fileContent == null) { + fileContent = new FileContentImpl(file); + } + updateSingleIndex(indexId, file, fileContent); + } + catch (StorageException e) { + LOG.info(e); + requestRebuild(indexId); + } + } + } + // For 'normal indices' schedule the file for update and stop iteration if at least one index accepts it + if (!isTooLarge(file)) { + for (ID indexId : myIndices.keySet()) { + if (needsFileContentLoading(indexId) && getInputFilter(indexId).acceptInput(file)) { + scheduleForUpdate(file); + break; // no need to iterate further, as the file is already marked + } + } + } + + return true; + } + }); + IndexingStamp.flushCache(); + } + + public void scheduleForUpdate(VirtualFile file) { + myFilesToUpdate.add(file); + } + + void invalidateIndices(@NotNull final VirtualFile file, final boolean markForReindex) { + if (isUnderConfigOrSystem(file)) { + return; + } + if (file.isDirectory()) { + if (isMock(file) || myManagingFS.wereChildrenAccessed(file)) { + final Iterable children = file instanceof NewVirtualFile + ? ((NewVirtualFile)file).iterInDbChildren() : Arrays.asList(file.getChildren()); + for (VirtualFile child : children) { + invalidateIndices(child, markForReindex); + } + } + } + else { + cleanProcessedFlag(file); + IndexingStamp.flushCache(); + final List> affectedIndices = new ArrayList>(myIndices.size()); + + for (final ID indexId : myIndices.keySet()) { + try { + if (!needsFileContentLoading(indexId)) { + if (shouldUpdateIndex(file, indexId)) { + updateSingleIndex(indexId, file, null); + } + } + else { // the index requires file content + if (shouldUpdateIndex(file, indexId)) { + affectedIndices.add(indexId); + } + } + } + catch (StorageException e) { + LOG.info(e); + requestRebuild(indexId); + } + } + + if (!affectedIndices.isEmpty()) { + if (markForReindex && !isTooLarge(file)) { + // only mark the file as unindexed, reindex will be done lazily + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + for (ID indexId : affectedIndices) { + IndexingStamp.update(file, indexId, -2L); + } + } + }); + // the file is for sure not a dir and it was previously indexed by at least one index + scheduleForUpdate(file); + } + else { + myFutureInvalidations.offer(new InvalidationTask(file) { + @Override + public void run() { + removeFileDataFromIndices(affectedIndices, file); + } + }); + } + } + if (!markForReindex) { + final boolean removedFromUpdateQueue = myFilesToUpdate.remove(file);// no need to update it anymore + if (removedFromUpdateQueue && affectedIndices.isEmpty()) { + // Currently the file is about to be deleted and previously it was scheduled for update and not processed up to now. + // Because the file was scheduled for update, at the moment of scheduling it was marked as unindexed, + // so, to be on the safe side, we have to schedule data invalidation from all content-requiring indices for this file + myFutureInvalidations.offer(new InvalidationTask(file) { + @Override + public void run() { + removeFileDataFromIndices(myRequiringContentIndices, file); + } + }); + } + } + + IndexingStamp.flushCache(); + } + } + + private void removeFileDataFromIndices(@NotNull Collection> affectedIndices, @NotNull VirtualFile file) { + Throwable unexpectedError = null; + for (ID indexId : affectedIndices) { + try { + updateSingleIndex(indexId, file, null); + } + catch (StorageException e) { + LOG.info(e); + requestRebuild(indexId); + } + catch (ProcessCanceledException ignored) { + } + catch (Throwable e) { + LOG.info(e); + if (unexpectedError == null) { + unexpectedError = e; + } + } + } + IndexingStamp.flushCache(); + if (unexpectedError != null) { + LOG.error(unexpectedError); + } + } + + public int getNumberOfPendingInvalidations() { + return myFutureInvalidations.size(); + } + + public void ensureAllInvalidateTasksCompleted() { + final int size = getNumberOfPendingInvalidations(); + if (size == 0) { + return; + } + final ProgressIndicator current = ProgressManager.getInstance().getProgressIndicator(); + final ProgressIndicator indicator = current != null ? current : new EmptyProgressIndicator(); + indicator.setText(""); + int count = 0; + while (true) { + InvalidationTask task = myFutureInvalidations.poll(); + + if (task == null) { + break; + } + indicator.setFraction((double)count++ /size); + indicator.setText2(task.getSubj().getPresentableUrl()); + task.run(); + } + } + + private void iterateIndexableFiles(@NotNull final VirtualFile file, @NotNull final Processor processor) { + if (file.isDirectory()) { + final ContentIterator iterator = new ContentIterator() { + @Override + public boolean processFile(@NotNull final VirtualFile fileOrDir) { + if (!fileOrDir.isDirectory()) { + processor.process(fileOrDir); + } + return true; + } + }; + + for (IndexableFileSet set : myIndexableSets) { + if (set.isInSet(file)) { + set.iterateIndexableFilesIn(file, iterator); + } + } + } + else { + for (IndexableFileSet set : myIndexableSets) { + if (set.isInSet(file)) { + processor.process(file); + break; + } + } + } + } + + public Collection getAllFilesToUpdate() { + if (myFilesToUpdate.isEmpty()) { + return Collections.emptyList(); + } + return new ArrayList(myFilesToUpdate); + } + + private final Semaphore myForceUpdateSemaphore = new Semaphore(); + + private void forceUpdate(@Nullable Project project, @Nullable GlobalSearchScope filter, @Nullable VirtualFile restrictedTo, + boolean onlyRemoveOutdatedData) { + myChangedFilesCollector.ensureAllInvalidateTasksCompleted(); + for (VirtualFile file: getAllFilesToUpdate()) { + if (filter == null || filter.accept(file) || file == restrictedTo) { + try { + myForceUpdateSemaphore.down(); + // process only files that can affect result + processFileImpl(project, new com.intellij.ide.caches.FileContent(file), onlyRemoveOutdatedData); + } + finally { + myForceUpdateSemaphore.up(); + } + } + } + + // If several threads entered the method at the same time and there were files to update, + // all the threads should leave the method synchronously after all the files scheduled for update are reindexed, + // no matter which thread will do reindexing job. + // Thus we ensure that all the threads that entered the method will get the most recent data + + while (!myForceUpdateSemaphore.waitFor(500)) { // may need to wait until another thread is done with indexing + if (Thread.holdsLock(PsiLock.LOCK)) { + break; // hack. Most probably that other indexing threads is waiting for PsiLock, which we're are holding. + } + } + } + + private void processFileImpl(Project project, @NotNull final com.intellij.ide.caches.FileContent fileContent, boolean onlyRemoveOutdatedData) { + final VirtualFile file = fileContent.getVirtualFile(); + final boolean reallyRemoved = myFilesToUpdate.remove(file); + if (reallyRemoved && file.isValid()) { + if (onlyRemoveOutdatedData) { + // on shutdown there is no need to re-index the file, just remove outdated data from indices + final List> affected = new ArrayList>(); + for (final ID indexId : myIndices.keySet()) { + if (getInputFilter(indexId).acceptInput(file)) { + affected.add(indexId); + } + } + removeFileDataFromIndices(affected, file); + } + else { + indexFileContent(project, fileContent); + } + IndexingStamp.flushCache(); + } + } + } + + private class UnindexedFilesFinder implements CollectingContentIterator { + private final List myFiles = new ArrayList(); + private final ProgressIndicator myProgressIndicator; + + private UnindexedFilesFinder() { + myProgressIndicator = ProgressManager.getInstance().getProgressIndicator(); + } + + @NotNull + @Override + public List getFiles() { + return myFiles; + } + + @Override + public boolean processFile(@NotNull final VirtualFile file) { + if (!file.isValid()) { + return true; + } + if (!file.isDirectory()) { + if (file instanceof NewVirtualFile && ((NewVirtualFile)file).getFlag(ALREADY_PROCESSED)) { + return true; + } + + if (file instanceof VirtualFileWithId) { + try { + FileTypeManagerImpl.cacheFileType(file, file.getFileType()); + + boolean oldStuff = true; + if (!isTooLarge(file)) { + for (ID indexId : myIndices.keySet()) { + try { + if (needsFileContentLoading(indexId) && shouldIndexFile(file, indexId)) { + myFiles.add(file); + oldStuff = false; + break; + } + } + catch (RuntimeException e) { + final Throwable cause = e.getCause(); + if (cause instanceof IOException || cause instanceof StorageException) { + LOG.info(e); + requestRebuild(indexId); + } + else { + throw e; + } + } + } + } + FileContent fileContent = null; + for (ID indexId : myNotRequiringContentIndices) { + if (shouldIndexFile(file, indexId)) { + oldStuff = false; + try { + if (fileContent == null) { + fileContent = new FileContentImpl(file); + } + updateSingleIndex(indexId, file, fileContent); + } + catch (StorageException e) { + LOG.info(e); + requestRebuild(indexId); + } + } + } + IndexingStamp.flushCache(); + + if (oldStuff && file instanceof NewVirtualFile) { + ((NewVirtualFile)file).setFlag(ALREADY_PROCESSED, true); + } + } + finally { + FileTypeManagerImpl.cacheFileType(file, null); + } + } + } + else { + if (myProgressIndicator != null) { + myProgressIndicator.setText("Scanning files to index"); + myProgressIndicator.setText2(file.getPresentableUrl()); + } + } + return true; + } + } + + private boolean shouldUpdateIndex(final VirtualFile file, final ID indexId) { + return getInputFilter(indexId).acceptInput(file) && + (isMock(file) || IndexingStamp.isFileIndexed(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId))); + } + + private boolean shouldIndexFile(final VirtualFile file, final ID indexId) { + return getInputFilter(indexId).acceptInput(file) && + (isMock(file) || !IndexingStamp.isFileIndexed(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId))); + } + + private boolean isUnderConfigOrSystem(@NotNull VirtualFile file) { + final String filePath = file.getPath(); + return myConfigPath != null && FileUtil.startsWith(filePath, myConfigPath) || + mySystemPath != null && FileUtil.startsWith(filePath, mySystemPath); + } + + private static boolean isMock(final VirtualFile file) { + return !(file instanceof NewVirtualFile); + } + + private boolean isTooLarge(@NotNull VirtualFile file) { + if (SingleRootFileViewProvider.isTooLarge(file)) { + final FileType type = file.getFileType(); + return !myNoLimitCheckTypes.contains(type); + } + return false; + } + + private boolean isTooLarge(@NotNull VirtualFile file, long contentSize) { + if (SingleRootFileViewProvider.isTooLarge(file, contentSize)) { + final FileType type = file.getFileType(); + return !myNoLimitCheckTypes.contains(type); + } + return false; + } + + @NotNull + public CollectingContentIterator createContentIterator() { + ++myFilesModCount; + return new UnindexedFilesFinder(); + } + + public void registerIndexableSet(@NotNull IndexableFileSet set, @Nullable Project project) { + myIndexableSets.add(set); + myIndexableSetToProjectMap.put(set, project); + } + + public void removeIndexableSet(@NotNull IndexableFileSet set) { + myChangedFilesCollector.forceUpdate(null, null, null, true); + myIndexableSets.remove(set); + myIndexableSetToProjectMap.remove(set); + } + + @Nullable + private static PsiFile findLatestKnownPsiForUncomittedDocument(@NotNull Document doc, @NotNull Project project) { + return PsiDocumentManager.getInstance(project).getCachedPsiFile(doc); + } + + private static class IndexableFilesFilter implements InputFilter { + private final InputFilter myDelegate; + + private IndexableFilesFilter(InputFilter delegate) { + myDelegate = delegate; + } + + @Override + public boolean acceptInput(final VirtualFile file) { + return file instanceof VirtualFileWithId && myDelegate.acceptInput(file); + } + } + + private static void cleanupProcessedFlag() { + final VirtualFile[] roots = ManagingFS.getInstance().getRoots(); + for (VirtualFile root : roots) { + cleanProcessedFlag(root); + } + } + + private static void cleanProcessedFlag(@NotNull final VirtualFile file) { + if (!(file instanceof NewVirtualFile)) return; + + final NewVirtualFile nvf = (NewVirtualFile)file; + if (file.isDirectory()) { + for (VirtualFile child : nvf.getCachedChildren()) { + cleanProcessedFlag(child); + } + } + else { + nvf.setFlag(ALREADY_PROCESSED, false); + } + } + + public static void iterateIndexableFiles(@NotNull final ContentIterator processor, @NotNull Project project, ProgressIndicator indicator) { + if (project.isDisposed()) { + return; + } + final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + // iterate project content + projectFileIndex.iterateContent(processor); + + if (project.isDisposed()) { + return; + } + + Set visitedRoots = new THashSet(); + for (IndexedRootsProvider provider : Extensions.getExtensions(IndexedRootsProvider.EP_NAME)) { + //important not to depend on project here, to support per-project background reindex + // each client gives a project to FileBasedIndex + if (project.isDisposed()) { + return; + } + for (VirtualFile root : IndexableSetContributor.getRootsToIndex(provider)) { + if (visitedRoots.add(root)) { + iterateRecursively(root, processor, indicator); + } + } + for (VirtualFile root : IndexableSetContributor.getProjectRootsToIndex(provider, project)) { + if (visitedRoots.add(root)) { + iterateRecursively(root, processor, indicator); + } + } + } + + if (project.isDisposed()) { + return; + } + // iterate associated libraries + for (Module module : ModuleManager.getInstance(project).getModules()) { + if (module.isDisposed()) { + return; + } + OrderEntry[] orderEntries = ModuleRootManager.getInstance(module).getOrderEntries(); + for (OrderEntry orderEntry : orderEntries) { + if (orderEntry instanceof LibraryOrderEntry || orderEntry instanceof JdkOrderEntry) { + if (orderEntry.isValid()) { + final VirtualFile[] libSources = orderEntry.getFiles(OrderRootType.SOURCES); + final VirtualFile[] libClasses = orderEntry.getFiles(OrderRootType.CLASSES); + for (VirtualFile[] roots : new VirtualFile[][]{libSources, libClasses}) { + for (VirtualFile root : roots) { + if (visitedRoots.add(root)) { + iterateRecursively(root, processor, indicator); + } + } + } + } + } + } + } + } + + private static void iterateRecursively(@Nullable final VirtualFile root, @NotNull final ContentIterator processor, @Nullable ProgressIndicator indicator) { + if (root != null) { + if (indicator != null) { + indicator.checkCanceled(); + indicator.setText2(root.getPresentableUrl()); + } + + if (root.isDirectory()) { + for (VirtualFile file : root.getChildren()) { + if (file.isDirectory()) { + iterateRecursively(file, processor, indicator); + } + else { + processor.processFile(file); + } + } + } + else { + processor.processFile(root); + } + } + } + + private static class StorageGuard { + private int myHolds = 0; + + public interface Holder { + void leave(); + } + + private final Holder myTrueHolder = new Holder() { + @Override + public void leave() { + StorageGuard.this.leave(true); + } + }; + private final Holder myFalseHolder = new Holder() { + @Override + public void leave() { + StorageGuard.this.leave(false); + } + }; + + @NotNull + public synchronized Holder enter(boolean mode) { + if (mode) { + while (myHolds < 0) { + try { + wait(); + } + catch (InterruptedException ignored) { + } + } + myHolds++; + return myTrueHolder; + } + else { + while (myHolds > 0) { + try { + wait(); + } + catch (InterruptedException ignored) { + } + } + myHolds--; + return myFalseHolder; + } + } + + private synchronized void leave(boolean mode) { + myHolds += mode? -1 : 1; + if (myHolds == 0) { + notifyAll(); + } + } + + } +} diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexProjectHandler.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexProjectHandler.java index 7e1994218251..13fee5b720ab 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexProjectHandler.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexProjectHandler.java @@ -42,12 +42,12 @@ import org.jetbrains.annotations.NotNull; import java.util.Collection; public class FileBasedIndexProjectHandler extends AbstractProjectComponent implements IndexableFileSet { - private final FileBasedIndex myIndex; + private final FileBasedIndexImpl myIndex; private final ProjectRootManagerEx myRootManager; private final FileTypeManager myFileTypeManager; private final ProjectFileExclusionManagerImpl myExclusionManager; - public FileBasedIndexProjectHandler(final FileBasedIndex index, final Project project, final ProjectRootManagerEx rootManager, FileTypeManager ftManager, final ProjectManager projectManager) { + public FileBasedIndexProjectHandler(final FileBasedIndexImpl index, final Project project, final ProjectRootManagerEx rootManager, FileTypeManager ftManager, final ProjectManager projectManager) { super(project); myIndex = index; myRootManager = rootManager; diff --git a/platform/lang-impl/src/com/intellij/util/indexing/UnindexedFilesUpdater.java b/platform/lang-impl/src/com/intellij/util/indexing/UnindexedFilesUpdater.java index d8ea84dc00b0..efb1445da0be 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/UnindexedFilesUpdater.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/UnindexedFilesUpdater.java @@ -33,11 +33,11 @@ import java.util.List; */ public class UnindexedFilesUpdater implements CacheUpdater { private static final Logger LOG = Logger.getInstance("#com.intellij.util.indexing.UnindexedFilesUpdater"); - private final FileBasedIndex myIndex; + private final FileBasedIndexImpl myIndex; private final Project myProject; private long myStarted; - public UnindexedFilesUpdater(final Project project, FileBasedIndex index) { + public UnindexedFilesUpdater(final Project project, FileBasedIndexImpl index) { myIndex = index; myProject = project; } @@ -51,7 +51,7 @@ public class UnindexedFilesUpdater implements CacheUpdater { public VirtualFile[] queryNeededFiles(ProgressIndicator indicator) { CollectingContentIterator finder = myIndex.createContentIterator(); long l = System.currentTimeMillis(); - FileBasedIndex.iterateIndexableFiles(finder, myProject, indicator); + FileBasedIndexImpl.iterateIndexableFiles(finder, myProject, indicator); LOG.info("Indexable files iterated in " + (System.currentTimeMillis() - l) + " ms"); List files = finder.getFiles(); LOG.info("Unindexed files update started: " + files.size() + " files to update"); diff --git a/platform/platform-resources/src/componentSets/Lang.xml b/platform/platform-resources/src/componentSets/Lang.xml index f4ce41d26018..54e0cc2a543c 100644 --- a/platform/platform-resources/src/componentSets/Lang.xml +++ b/platform/platform-resources/src/componentSets/Lang.xml @@ -12,7 +12,8 @@ - com.intellij.util.indexing.FileBasedIndex + com.intellij.util.indexing.FileBasedIndex + com.intellij.util.indexing.FileBasedIndexImpl com.intellij.psi.stubs.StubIndex diff --git a/platform/testFramework/src/com/intellij/testFramework/LightPlatformTestCase.java b/platform/testFramework/src/com/intellij/testFramework/LightPlatformTestCase.java index a8ba4077789d..a7e2d39a9746 100644 --- a/platform/testFramework/src/com/intellij/testFramework/LightPlatformTestCase.java +++ b/platform/testFramework/src/com/intellij/testFramework/LightPlatformTestCase.java @@ -93,6 +93,7 @@ import com.intellij.util.IncorrectOperationException; import com.intellij.util.LocalTimeCounter; import com.intellij.util.containers.CollectionFactory; import com.intellij.util.indexing.FileBasedIndex; +import com.intellij.util.indexing.FileBasedIndexImpl; import com.intellij.util.indexing.IndexableFileSet; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.ui.UIUtil; @@ -234,7 +235,7 @@ public abstract class LightPlatformTestCase extends UsefulTestCase implements Da throw new RuntimeException(e); } - FileBasedIndex.getInstance().registerIndexableSet(new IndexableFileSet() { + ((FileBasedIndexImpl)FileBasedIndex.getInstance()).registerIndexableSet(new IndexableFileSet() { @Override public boolean isInSet(@NotNull final VirtualFile file) { return ourSourceRoot != null && file.getFileSystem() == ourSourceRoot.getFileSystem() && ourProject.isOpen(); From 3bb4da93ec56c48fa937a6d4554ff066258710e7 Mon Sep 17 00:00:00 2001 From: Dmitry Lomov Date: Wed, 9 May 2012 11:02:03 +0200 Subject: [PATCH 18/31] Extra changes --- .../src/com/intellij/util/indexing/FileBasedIndex.java | 4 ++++ .../src/com/intellij/util/indexing/FileBasedIndexImpl.java | 3 ++- .../src/com/intellij/util/indexing/UnindexedFilesUpdater.java | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java index c2f3ad423b19..3ef57cafca3a 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java @@ -17,7 +17,9 @@ package com.intellij.util.indexing; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ContentIterator; import com.intellij.openapi.util.Condition; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileWithId; @@ -35,6 +37,8 @@ import java.util.Set; * Author: dmitrylomov */ public abstract class FileBasedIndex implements ApplicationComponent { + public abstract void iterateIndexableFiles(@NotNull ContentIterator processor, @NotNull Project project, ProgressIndicator indicator); + public static FileBasedIndex getInstance() { return ApplicationManager.getApplication().getComponent(FileBasedIndex.class); } diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java index 1cf3b2677d89..29e82dd6d2a6 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java @@ -2189,7 +2189,8 @@ public class FileBasedIndexImpl extends FileBasedIndex { } } - public static void iterateIndexableFiles(@NotNull final ContentIterator processor, @NotNull Project project, ProgressIndicator indicator) { + @Override + public void iterateIndexableFiles(@NotNull final ContentIterator processor, @NotNull Project project, ProgressIndicator indicator) { if (project.isDisposed()) { return; } diff --git a/platform/lang-impl/src/com/intellij/util/indexing/UnindexedFilesUpdater.java b/platform/lang-impl/src/com/intellij/util/indexing/UnindexedFilesUpdater.java index efb1445da0be..a0e42f63198d 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/UnindexedFilesUpdater.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/UnindexedFilesUpdater.java @@ -51,7 +51,7 @@ public class UnindexedFilesUpdater implements CacheUpdater { public VirtualFile[] queryNeededFiles(ProgressIndicator indicator) { CollectingContentIterator finder = myIndex.createContentIterator(); long l = System.currentTimeMillis(); - FileBasedIndexImpl.iterateIndexableFiles(finder, myProject, indicator); + FileBasedIndex.getInstance().iterateIndexableFiles(finder, myProject, indicator); LOG.info("Indexable files iterated in " + (System.currentTimeMillis() - l) + " ms"); List files = finder.getFiles(); LOG.info("Unindexed files update started: " + files.size() + " files to update"); From 29dfa01cf2eb6bc230626a108c433d41728c2cac Mon Sep 17 00:00:00 2001 From: Dmitry Lomov Date: Wed, 9 May 2012 11:32:20 +0200 Subject: [PATCH 19/31] Extra methods --- .../src/com/intellij/util/indexing/FileBasedIndex.java | 4 ++++ .../src/com/intellij/util/indexing/FileBasedIndexImpl.java | 2 ++ 2 files changed, 6 insertions(+) diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java index 3ef57cafca3a..f2dad634cf9f 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java @@ -39,6 +39,10 @@ import java.util.Set; public abstract class FileBasedIndex implements ApplicationComponent { public abstract void iterateIndexableFiles(@NotNull ContentIterator processor, @NotNull Project project, ProgressIndicator indicator); + public abstract void registerIndexableSet(@NotNull IndexableFileSet set, @Nullable Project project); + + public abstract void removeIndexableSet(@NotNull IndexableFileSet set); + public static FileBasedIndex getInstance() { return ApplicationManager.getApplication().getComponent(FileBasedIndex.class); } diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java index 29e82dd6d2a6..b6043d4bef02 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java @@ -2139,11 +2139,13 @@ public class FileBasedIndexImpl extends FileBasedIndex { return new UnindexedFilesFinder(); } + @Override public void registerIndexableSet(@NotNull IndexableFileSet set, @Nullable Project project) { myIndexableSets.add(set); myIndexableSetToProjectMap.put(set, project); } + @Override public void removeIndexableSet(@NotNull IndexableFileSet set) { myChangedFilesCollector.forceUpdate(null, null, null, true); myIndexableSets.remove(set); From 7c677fc88ae36f801dbb827fe7be1a5247392571 Mon Sep 17 00:00:00 2001 From: Dmitry Lomov Date: Wed, 9 May 2012 12:07:10 +0200 Subject: [PATCH 20/31] More fixed --- .../src/com/intellij/util/indexing/FileBasedIndex.java | 5 ----- .../src/com/intellij/util/indexing/FileBasedIndexImpl.java | 1 - 2 files changed, 6 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java index f2dad634cf9f..0c7c23ce6f62 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java @@ -91,11 +91,6 @@ public abstract class FileBasedIndex implements ApplicationComponent { public abstract void ensureUpToDate(@NotNull ID indexId, @Nullable Project project, @Nullable GlobalSearchScope filter); - protected abstract void ensureUpToDate(@NotNull ID indexId, - @Nullable Project project, - @Nullable GlobalSearchScope filter, - @Nullable VirtualFile restrictedFile); - public abstract void requestRebuild(ID indexId, Throwable throwable); public abstract void scheduleRebuild(@NotNull ID indexId, @NotNull Throwable e); diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java index b6043d4bef02..a5f15bf670d1 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java @@ -737,7 +737,6 @@ public class FileBasedIndexImpl extends FileBasedIndex { ensureUpToDate(indexId, project, filter, null); } - @Override protected void ensureUpToDate(@NotNull final ID indexId, @Nullable Project project, @Nullable GlobalSearchScope filter, From 1a49cf6aa4befc280320fc71f975a71cdc9c01e0 Mon Sep 17 00:00:00 2001 From: Dmitry Lomov Date: Tue, 8 May 2012 18:26:13 +0200 Subject: [PATCH 21/31] FileBasedIndex --- .../src/com/intellij/util/indexing/FileBasedIndex.java | 6 ++++++ .../src/com/intellij/util/indexing/FileBasedIndexImpl.java | 2 +- .../com/intellij/util/indexing/UnindexedFilesUpdater.java | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java index 0c7c23ce6f62..378801652209 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java @@ -37,6 +37,7 @@ import java.util.Set; * Author: dmitrylomov */ public abstract class FileBasedIndex implements ApplicationComponent { + public abstract void iterateIndexableFiles(@NotNull ContentIterator processor, @NotNull Project project, ProgressIndicator indicator); public abstract void registerIndexableSet(@NotNull IndexableFileSet set, @Nullable Project project); @@ -91,6 +92,11 @@ public abstract class FileBasedIndex implements ApplicationComponent { public abstract void ensureUpToDate(@NotNull ID indexId, @Nullable Project project, @Nullable GlobalSearchScope filter); + protected abstract void ensureUpToDate(@NotNull ID indexId, + @Nullable Project project, + @Nullable GlobalSearchScope filter, + @Nullable VirtualFile restrictedFile); + public abstract void requestRebuild(ID indexId, Throwable throwable); public abstract void scheduleRebuild(@NotNull ID indexId, @NotNull Throwable e); diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java index a5f15bf670d1..d658dd11616b 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java @@ -737,6 +737,7 @@ public class FileBasedIndexImpl extends FileBasedIndex { ensureUpToDate(indexId, project, filter, null); } + @Override protected void ensureUpToDate(@NotNull final ID indexId, @Nullable Project project, @Nullable GlobalSearchScope filter, @@ -2144,7 +2145,6 @@ public class FileBasedIndexImpl extends FileBasedIndex { myIndexableSetToProjectMap.put(set, project); } - @Override public void removeIndexableSet(@NotNull IndexableFileSet set) { myChangedFilesCollector.forceUpdate(null, null, null, true); myIndexableSets.remove(set); diff --git a/platform/lang-impl/src/com/intellij/util/indexing/UnindexedFilesUpdater.java b/platform/lang-impl/src/com/intellij/util/indexing/UnindexedFilesUpdater.java index a0e42f63198d..ad0c20902d73 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/UnindexedFilesUpdater.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/UnindexedFilesUpdater.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 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. From 6d61ee0df41876a48764bc4bf7d832663bfb69d3 Mon Sep 17 00:00:00 2001 From: Dmitry Lomov Date: Wed, 9 May 2012 11:02:03 +0200 Subject: [PATCH 22/31] Extra changes --- .../lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java | 1 - 1 file changed, 1 deletion(-) diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java index 378801652209..f2dad634cf9f 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java @@ -37,7 +37,6 @@ import java.util.Set; * Author: dmitrylomov */ public abstract class FileBasedIndex implements ApplicationComponent { - public abstract void iterateIndexableFiles(@NotNull ContentIterator processor, @NotNull Project project, ProgressIndicator indicator); public abstract void registerIndexableSet(@NotNull IndexableFileSet set, @Nullable Project project); From 4dfdbfb0be9a94ad8b029145e791de9ca8ef5047 Mon Sep 17 00:00:00 2001 From: Dmitry Lomov Date: Wed, 9 May 2012 11:32:20 +0200 Subject: [PATCH 23/31] Extra methods --- .../src/com/intellij/util/indexing/FileBasedIndexImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java index d658dd11616b..b6043d4bef02 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java @@ -2145,6 +2145,7 @@ public class FileBasedIndexImpl extends FileBasedIndex { myIndexableSetToProjectMap.put(set, project); } + @Override public void removeIndexableSet(@NotNull IndexableFileSet set) { myChangedFilesCollector.forceUpdate(null, null, null, true); myIndexableSets.remove(set); From 414a38d0aa1044277e52cf5d2495a31e9143c64f Mon Sep 17 00:00:00 2001 From: Dmitry Lomov Date: Wed, 9 May 2012 12:07:10 +0200 Subject: [PATCH 24/31] More fixed --- .../src/com/intellij/util/indexing/FileBasedIndex.java | 5 ----- .../src/com/intellij/util/indexing/FileBasedIndexImpl.java | 1 - 2 files changed, 6 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java index f2dad634cf9f..0c7c23ce6f62 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java @@ -91,11 +91,6 @@ public abstract class FileBasedIndex implements ApplicationComponent { public abstract void ensureUpToDate(@NotNull ID indexId, @Nullable Project project, @Nullable GlobalSearchScope filter); - protected abstract void ensureUpToDate(@NotNull ID indexId, - @Nullable Project project, - @Nullable GlobalSearchScope filter, - @Nullable VirtualFile restrictedFile); - public abstract void requestRebuild(ID indexId, Throwable throwable); public abstract void scheduleRebuild(@NotNull ID indexId, @NotNull Throwable e); diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java index b6043d4bef02..a5f15bf670d1 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java @@ -737,7 +737,6 @@ public class FileBasedIndexImpl extends FileBasedIndex { ensureUpToDate(indexId, project, filter, null); } - @Override protected void ensureUpToDate(@NotNull final ID indexId, @Nullable Project project, @Nullable GlobalSearchScope filter, From 4805c0ebf3c6dd386bb11665db7dd889bf8d4bf6 Mon Sep 17 00:00:00 2001 From: Evgeny Pasynkov Date: Wed, 9 May 2012 13:35:15 +0200 Subject: [PATCH 25/31] +CoreJavaCodeStyleManager --- .../com/intellij/core/CoreJavaCodeStyleManager.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 java/java-psi-impl/src/com/intellij/core/CoreJavaCodeStyleManager.java diff --git a/java/java-psi-impl/src/com/intellij/core/CoreJavaCodeStyleManager.java b/java/java-psi-impl/src/com/intellij/core/CoreJavaCodeStyleManager.java new file mode 100644 index 000000000000..c5632dfdcf6f --- /dev/null +++ b/java/java-psi-impl/src/com/intellij/core/CoreJavaCodeStyleManager.java @@ -0,0 +1,11 @@ +package com.intellij.core; + +/** + * Created with IntelliJ IDEA. + * User: pasynkov + * Date: 09.05.12 + * Time: 13:22 + * To change this template use File | Settings | File Templates. + */ +public class CoreJavaCodeStyleManager { +} From 40244034d5ff9bc9a70d9532a6be1e1a26c3b695 Mon Sep 17 00:00:00 2001 From: Dmitry Lomov Date: Wed, 9 May 2012 13:36:53 +0200 Subject: [PATCH 26/31] FileBasedIndex moved to indexing-impl --- .../src/com/intellij/openapi/roots/ContentIterator.java | 0 .../src/com/intellij/util/indexing/FileBasedIndex.java | 4 ++-- .../src/com/intellij/util/indexing/IndexableFileSet.java | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename platform/{platform-api => core-api}/src/com/intellij/openapi/roots/ContentIterator.java (100%) rename platform/{lang-impl => indexing-api}/src/com/intellij/util/indexing/FileBasedIndex.java (97%) rename platform/{lang-impl => indexing-api}/src/com/intellij/util/indexing/IndexableFileSet.java (100%) diff --git a/platform/platform-api/src/com/intellij/openapi/roots/ContentIterator.java b/platform/core-api/src/com/intellij/openapi/roots/ContentIterator.java similarity index 100% rename from platform/platform-api/src/com/intellij/openapi/roots/ContentIterator.java rename to platform/core-api/src/com/intellij/openapi/roots/ContentIterator.java diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java b/platform/indexing-api/src/com/intellij/util/indexing/FileBasedIndex.java similarity index 97% rename from platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java rename to platform/indexing-api/src/com/intellij/util/indexing/FileBasedIndex.java index 0c7c23ce6f62..afcb32543471 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndex.java +++ b/platform/indexing-api/src/com/intellij/util/indexing/FileBasedIndex.java @@ -16,7 +16,7 @@ package com.intellij.util.indexing; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.components.BaseComponent; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ContentIterator; @@ -36,7 +36,7 @@ import java.util.Set; /** * Author: dmitrylomov */ -public abstract class FileBasedIndex implements ApplicationComponent { +public abstract class FileBasedIndex implements BaseComponent { public abstract void iterateIndexableFiles(@NotNull ContentIterator processor, @NotNull Project project, ProgressIndicator indicator); public abstract void registerIndexableSet(@NotNull IndexableFileSet set, @Nullable Project project); diff --git a/platform/lang-impl/src/com/intellij/util/indexing/IndexableFileSet.java b/platform/indexing-api/src/com/intellij/util/indexing/IndexableFileSet.java similarity index 100% rename from platform/lang-impl/src/com/intellij/util/indexing/IndexableFileSet.java rename to platform/indexing-api/src/com/intellij/util/indexing/IndexableFileSet.java From 82497269425ddf1701ea7bf5f9925132a2773e04 Mon Sep 17 00:00:00 2001 From: Dmitry Lomov Date: Wed, 9 May 2012 13:37:05 +0200 Subject: [PATCH 27/31] FileBasedIndex moved to indexing-impl --- .../src/com/intellij/compiler/impl/CompilerContentIterator.java | 1 - .../testSrc/com/intellij/psi/impl/cache/impl/IdCacheTest.java | 1 - .../testSrc/com/intellij/psi/search/UpdateCacheTest.java | 1 - .../src/com/intellij/testFramework/LightPlatformTestCase.java | 1 - 4 files changed, 4 deletions(-) diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompilerContentIterator.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompilerContentIterator.java index 74bba4091d7b..c7634804a18d 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/CompilerContentIterator.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompilerContentIterator.java @@ -19,7 +19,6 @@ import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.roots.ContentIterator; import com.intellij.openapi.roots.FileIndex; -import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import java.util.Collection; diff --git a/java/java-tests/testSrc/com/intellij/psi/impl/cache/impl/IdCacheTest.java b/java/java-tests/testSrc/com/intellij/psi/impl/cache/impl/IdCacheTest.java index eaf7ae618ffe..bb43aac5a141 100644 --- a/java/java-tests/testSrc/com/intellij/psi/impl/cache/impl/IdCacheTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/impl/cache/impl/IdCacheTest.java @@ -21,7 +21,6 @@ import com.intellij.psi.search.UsageSearchContext; import com.intellij.testFramework.PsiTestUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.indexing.FileBasedIndex; -import com.intellij.util.indexing.FileBasedIndexImpl; import java.io.File; import java.util.Arrays; diff --git a/java/java-tests/testSrc/com/intellij/psi/search/UpdateCacheTest.java b/java/java-tests/testSrc/com/intellij/psi/search/UpdateCacheTest.java index be90964eafae..9ed5627cc09b 100644 --- a/java/java-tests/testSrc/com/intellij/psi/search/UpdateCacheTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/search/UpdateCacheTest.java @@ -44,7 +44,6 @@ import com.intellij.testFramework.PsiTestUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.Processor; import com.intellij.util.indexing.FileBasedIndex; -import com.intellij.util.indexing.FileBasedIndexImpl; import org.jetbrains.annotations.NonNls; import java.io.File; diff --git a/platform/testFramework/src/com/intellij/testFramework/LightPlatformTestCase.java b/platform/testFramework/src/com/intellij/testFramework/LightPlatformTestCase.java index a7e2d39a9746..2078af288a71 100644 --- a/platform/testFramework/src/com/intellij/testFramework/LightPlatformTestCase.java +++ b/platform/testFramework/src/com/intellij/testFramework/LightPlatformTestCase.java @@ -56,7 +56,6 @@ import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.project.ModuleAdapter; -import com.intellij.openapi.project.ModuleListener; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.project.ex.ProjectEx; From bdc83cd8f62f258189cb60da854a026b825deef5 Mon Sep 17 00:00:00 2001 From: Evgeny Pasynkov Date: Wed, 9 May 2012 13:40:34 +0200 Subject: [PATCH 28/31] +CoreJavaCodeStyleManager --- .../core/CoreJavaCodeStyleManager.java | 134 ++++++++++++++++-- .../intellij/core/JavaCoreEnvironment.java | 2 + 2 files changed, 128 insertions(+), 8 deletions(-) diff --git a/java/java-psi-impl/src/com/intellij/core/CoreJavaCodeStyleManager.java b/java/java-psi-impl/src/com/intellij/core/CoreJavaCodeStyleManager.java index c5632dfdcf6f..7f651a21df0e 100644 --- a/java/java-psi-impl/src/com/intellij/core/CoreJavaCodeStyleManager.java +++ b/java/java-psi-impl/src/com/intellij/core/CoreJavaCodeStyleManager.java @@ -1,11 +1,129 @@ +/* + * 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.core; -/** - * Created with IntelliJ IDEA. - * User: pasynkov - * Date: 09.05.12 - * Time: 13:22 - * To change this template use File | Settings | File Templates. - */ -public class CoreJavaCodeStyleManager { +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.codeStyle.SuggestedNameInfo; +import com.intellij.psi.codeStyle.VariableKind; +import com.intellij.util.IncorrectOperationException; +import org.intellij.lang.annotations.MagicConstant; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +public class CoreJavaCodeStyleManager extends JavaCodeStyleManager { + @Override + public boolean addImport(@NotNull PsiJavaFile file, @NotNull PsiClass refClass) { + return false; + } + + @Override + public PsiElement shortenClassReferences(@NotNull PsiElement element, + @MagicConstant(flags = {DO_NOT_ADD_IMPORTS, UNCOMPLETE_CODE}) int flags) + throws IncorrectOperationException { + return null; + } + + @NotNull + @Override + public String getPrefixByVariableKind(VariableKind variableKind) { + return ""; + } + + @NotNull + @Override + public String getSuffixByVariableKind(VariableKind variableKind) { + return ""; + } + + @Override + public int findEntryIndex(@NotNull PsiImportStatementBase statement) { + return 0; + } + + @Override + public PsiElement shortenClassReferences(@NotNull PsiElement element) throws IncorrectOperationException { + return null; + } + + @Override + public void shortenClassReferences(@NotNull PsiElement element, int startOffset, int endOffset) throws IncorrectOperationException { + } + + @Override + public void optimizeImports(@NotNull PsiFile file) throws IncorrectOperationException { + } + + @Override + public PsiImportList prepareOptimizeImportsResult(@NotNull PsiJavaFile file) { + return null; + } + + @Override + public VariableKind getVariableKind(@NotNull PsiVariable variable) { + return null; + } + + @Override + public SuggestedNameInfo suggestVariableName(@NotNull VariableKind kind, + @Nullable String propertyName, + @Nullable PsiExpression expr, + @Nullable PsiType type, + boolean correctKeywords) { + return null; + } + + @Override + public String variableNameToPropertyName(@NonNls String name, VariableKind variableKind) { + return null; + } + + @Override + public String propertyNameToVariableName(@NonNls String propertyName, VariableKind variableKind) { + return null; + } + + @Override + public String suggestUniqueVariableName(@NonNls String baseName, PsiElement place, boolean lookForward) { + return null; + } + + @NotNull + @Override + public SuggestedNameInfo suggestUniqueVariableName(@NotNull SuggestedNameInfo baseNameInfo, + PsiElement place, + boolean ignorePlaceName, + boolean lookForward) { + return SuggestedNameInfo.NULL_INFO; + } + + @Override + public PsiElement qualifyClassReferences(@NotNull PsiElement element) { + return null; + } + + @Override + public void removeRedundantImports(@NotNull PsiJavaFile file) throws IncorrectOperationException { + } + + @Override + public Collection findRedundantImports(PsiJavaFile file) { + return null; + } } diff --git a/java/java-psi-impl/src/com/intellij/core/JavaCoreEnvironment.java b/java/java-psi-impl/src/com/intellij/core/JavaCoreEnvironment.java index 46aa4080730f..16cdc8d46e90 100644 --- a/java/java-psi-impl/src/com/intellij/core/JavaCoreEnvironment.java +++ b/java/java-psi-impl/src/com/intellij/core/JavaCoreEnvironment.java @@ -30,6 +30,7 @@ import com.intellij.openapi.roots.PackageIndex; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.augment.PsiAugmentProvider; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.codeStyle.JavaCodeStyleSettingsFacade; import com.intellij.psi.impl.EmptySubstitutorImpl; import com.intellij.psi.impl.JavaPsiFacadeImpl; @@ -79,6 +80,7 @@ public class JavaCoreEnvironment extends CoreEnvironment { myProject.registerService(PackageIndex.class, myFileManager); myProject.registerService(JavaResolveCache.class, new JavaResolveCache(null)); myProject.registerService(JavaCodeStyleSettingsFacade.class, new CoreJavaCodeStyleSettingsFacade()); + myProject.registerService(JavaCodeStyleManager.class, new CoreJavaCodeStyleManager()); JavaPsiFacadeImpl javaPsiFacade = new JavaPsiFacadeImpl(myProject, myPsiManager, myFileManager, null); myProject.registerService(CoreJavaFileManager.class, myFileManager); From b0b991da7e70da244cd0666a0ed053a95bbb3f74 Mon Sep 17 00:00:00 2001 From: Evgeny Pasynkov Date: Wed, 9 May 2012 14:58:39 +0200 Subject: [PATCH 29/31] Better stub CoreJavaCodeStyleManager --- .../codeStyle/JavaCodeStyleManagerImpl.java | 2100 ++++++++--------- .../psi/codeStyle/JavaCodeStyleManager.java | 407 ++-- .../core/CoreJavaCodeStyleManager.java | 9 +- 3 files changed, 1254 insertions(+), 1262 deletions(-) diff --git a/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/JavaCodeStyleManagerImpl.java b/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/JavaCodeStyleManagerImpl.java index ed7ec70859e1..76ac3ac6a49b 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/JavaCodeStyleManagerImpl.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/codeStyle/JavaCodeStyleManagerImpl.java @@ -1,1061 +1,1039 @@ -/* - * Copyright 2000-2011 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. - */ - -/* - * @author max - */ -package com.intellij.psi.impl.source.codeStyle; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.util.text.StringUtilRt; -import com.intellij.pom.java.LanguageLevel; -import com.intellij.psi.*; -import com.intellij.psi.codeStyle.*; -import com.intellij.psi.impl.CheckUtil; -import com.intellij.psi.impl.source.SourceTreeToPsiMap; -import com.intellij.psi.impl.source.jsp.jspJava.JspxImportStatement; -import com.intellij.psi.impl.source.tree.TreeElement; -import com.intellij.psi.statistics.JavaStatisticsManager; -import com.intellij.psi.util.*; -import com.intellij.util.ArrayUtil; -import com.intellij.util.IncorrectOperationException; -import com.intellij.util.containers.ContainerUtil; -import gnu.trove.THashSet; -import gnu.trove.TObjectHashingStrategy; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.beans.Introspector; -import java.util.*; - -public class JavaCodeStyleManagerImpl extends JavaCodeStyleManager { - private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.codeStyle.JavaCodeStyleManagerImpl"); - - @NonNls private static final String IMPL_TYPNAME_SUFFIX = "Impl"; - @NonNls private static final String GET_PREFIX = "get"; - @NonNls private static final String IS_PREFIX = "is"; - @NonNls private static final String FIND_PREFIX = "find"; - @NonNls private static final String CREATE_PREFIX = "create"; - @NonNls private static final String WITH_PREFIX = "with"; - @NonNls private static final String SET_PREFIX = "set"; - - private final Project myProject; - - public JavaCodeStyleManagerImpl(final Project project) { - myProject = project; - } - - @Override - public PsiElement shortenClassReferences(@NotNull PsiElement element) throws IncorrectOperationException { - return shortenClassReferences(element, 0); - } - - @Override - public PsiElement shortenClassReferences(@NotNull PsiElement element, int flags) throws IncorrectOperationException { - CheckUtil.checkWritable(element); - if (!SourceTreeToPsiMap.hasTreeElement(element)) return element; - - return SourceTreeToPsiMap.treeElementToPsi( - new ReferenceAdjuster(myProject).process((TreeElement)element.getNode(), (flags & DO_NOT_ADD_IMPORTS) == 0, - (flags & UNCOMPLETE_CODE) != 0)); - } - - @Override - public void shortenClassReferences(@NotNull PsiElement element, int startOffset, int endOffset) - throws IncorrectOperationException { - CheckUtil.checkWritable(element); - if (SourceTreeToPsiMap.hasTreeElement(element)) { - new ReferenceAdjuster(myProject).processRange((TreeElement)element.getNode(), startOffset, endOffset); - } - } - - @Override - public PsiElement qualifyClassReferences(@NotNull PsiElement element) { - return SourceTreeToPsiMap.treeElementToPsi(new ReferenceAdjuster(true, true).process((TreeElement)element.getNode(), false, false)); - } - - @Override - public void optimizeImports(@NotNull PsiFile file) throws IncorrectOperationException { - CheckUtil.checkWritable(file); - if (file instanceof PsiJavaFile) { - PsiImportList newList = prepareOptimizeImportsResult((PsiJavaFile)file); - if (newList != null) { - final PsiImportList importList = ((PsiJavaFile)file).getImportList(); - if (importList != null) { - importList.replace(newList); - } - } - } - } - - @Override - public PsiImportList prepareOptimizeImportsResult(@NotNull PsiJavaFile file) { - return new ImportHelper(getSettings()).prepareOptimizeImportsResult(file); - } - - @Override - public boolean addImport(@NotNull PsiJavaFile file, @NotNull PsiClass refClass) { - return new ImportHelper(getSettings()).addImport(file, refClass); - } - - @Override - public void removeRedundantImports(@NotNull final PsiJavaFile file) throws IncorrectOperationException { - final Collection redundants = findRedundantImports(file); - if (redundants == null) return; - - for (final PsiImportStatementBase importStatement : redundants) { - final PsiJavaCodeReferenceElement ref = importStatement.getImportReference(); - //Do not remove non-resolving refs - if (ref == null || ref.resolve() == null) { - continue; - } - - importStatement.delete(); - } - } - - @Override - @Nullable - public Collection findRedundantImports(final PsiJavaFile file) { - final PsiImportList importList = file.getImportList(); - if (importList == null) return null; - final PsiImportStatementBase[] imports = importList.getAllImportStatements(); - if( imports.length == 0 ) return null; - - Set allImports = new THashSet(Arrays.asList(imports)); - final Collection redundants; - if (JspPsiUtil.isInJspFile(file)) { - // remove only duplicate imports - redundants = new THashSet(TObjectHashingStrategy.IDENTITY); - ContainerUtil.addAll(redundants, imports); - redundants.removeAll(allImports); - for (PsiImportStatementBase importStatement : imports) { - if (importStatement instanceof JspxImportStatement && ((JspxImportStatement)importStatement).isForeignFileImport()) { - redundants.remove(importStatement); - } - } - } - else { - redundants = allImports; - final List roots = file.getViewProvider().getAllFiles(); - for (PsiElement root : roots) { - root.accept(new JavaRecursiveElementWalkingVisitor() { - @Override public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { - if (!reference.isQualified()) { - final JavaResolveResult resolveResult = reference.advancedResolve(false); - if (!inTheSamePackage(file, resolveResult.getElement())) { - final PsiElement resolveScope = resolveResult.getCurrentFileResolveScope(); - if (resolveScope instanceof PsiImportStatementBase) { - final PsiImportStatementBase importStatementBase = (PsiImportStatementBase)resolveScope; - redundants.remove(importStatementBase); - } - } - } - super.visitReferenceElement(reference); - } - - private boolean inTheSamePackage(PsiJavaFile file, PsiElement element) { - if (element instanceof PsiClass && ((PsiClass)element).getContainingClass() == null) { - final PsiFile containingFile = element.getContainingFile(); - if (containingFile instanceof PsiJavaFile) { - return Comparing.strEqual(file.getPackageName(), ((PsiJavaFile)containingFile).getPackageName()); - } - } - return false; - } - }); - } - } - return redundants; - } - - @Override - public int findEntryIndex(@NotNull PsiImportStatementBase statement) { - return new ImportHelper(getSettings()).findEntryIndex(statement); - } - - @Override - public VariableKind getVariableKind(@NotNull PsiVariable variable) { - if (variable instanceof PsiField) { - if (variable.hasModifierProperty(PsiModifier.STATIC)) { - if (variable.hasModifierProperty(PsiModifier.FINAL)) { - return VariableKind.STATIC_FINAL_FIELD; - } - return VariableKind.STATIC_FIELD; - } - return VariableKind.FIELD; - } - else { - if (variable instanceof PsiParameter) { - if (((PsiParameter)variable).getDeclarationScope() instanceof PsiForeachStatement) { - return VariableKind.LOCAL_VARIABLE; - } - return VariableKind.PARAMETER; - } - return VariableKind.LOCAL_VARIABLE; - } - } - - @Override - public SuggestedNameInfo suggestVariableName(@NotNull final VariableKind kind, - @Nullable final String propertyName, - @Nullable final PsiExpression expr, - @Nullable PsiType type, - final boolean correctKeywords) { - LinkedHashSet names = new LinkedHashSet(); - - if (expr != null && type == null) { - type = expr.getType(); - } - - if (propertyName != null) { - String[] namesByName = getSuggestionsByName(propertyName, kind, false, correctKeywords); - sortVariableNameSuggestions(namesByName, kind, propertyName, null); - ContainerUtil.addAll(names, namesByName); - } - - final NamesByExprInfo namesByExpr; - if (expr != null) { - namesByExpr = suggestVariableNameByExpression(expr, kind, correctKeywords); - if (namesByExpr.propertyName != null) { - sortVariableNameSuggestions(namesByExpr.names, kind, namesByExpr.propertyName, null); - } - ContainerUtil.addAll(names, namesByExpr.names); - } - else { - namesByExpr = null; - } - - if (type != null) { - String[] namesByType = suggestVariableNameByType(type, kind, correctKeywords); - sortVariableNameSuggestions(namesByType, kind, null, type); - ContainerUtil.addAll(names, namesByType); - } - - final String _propertyName; - if (propertyName != null) { - _propertyName = propertyName; - } - else { - _propertyName = namesByExpr != null ? namesByExpr.propertyName : null; - } - - addNamesFromStatistics(names, kind, _propertyName, type); - - String[] namesArray = ArrayUtil.toStringArray(names); - sortVariableNameSuggestions(namesArray, kind, _propertyName, type); - - final PsiType _type = type; - return new SuggestedNameInfo(namesArray) { - @Override - public void nameChoosen(String name) { - if (_propertyName != null || _type != null && _type.isValid()) { - JavaStatisticsManager.incVariableNameUseCount(name, kind, _propertyName, _type); - } - } - }; - } - - private static void addNamesFromStatistics(Set names, VariableKind variableKind, @Nullable String propertyName, @Nullable PsiType type) { - String[] allNames = JavaStatisticsManager.getAllVariableNamesUsed(variableKind, propertyName, type); - - int maxFrequency = 0; - for (String name : allNames) { - int count = JavaStatisticsManager.getVariableNameUseCount(name, variableKind, propertyName, type); - maxFrequency = Math.max(maxFrequency, count); - } - - int frequencyLimit = Math.max(5, maxFrequency / 2); - - for (String name : allNames) { - if( names.contains( name ) ) - { - continue; - } - int count = JavaStatisticsManager.getVariableNameUseCount(name, variableKind, propertyName, type); - if (LOG.isDebugEnabled()) { - LOG.debug("new name:" + name + " count:" + count); - LOG.debug("frequencyLimit:" + frequencyLimit); - } - if (count >= frequencyLimit) { - names.add(name); - } - } - - if (propertyName != null && type != null) { - addNamesFromStatistics(names, variableKind, propertyName, null); - addNamesFromStatistics(names, variableKind, null, type); - } - } - - private String[] suggestVariableNameByType(PsiType type, final VariableKind variableKind, boolean correctKeywords) { - String longTypeName = getLongTypeName(type); - CodeStyleSettings.TypeToNameMap map = getMapByVariableKind(variableKind); - if (map != null && longTypeName != null) { - if (type.equals(PsiType.NULL)) { - longTypeName = CommonClassNames.JAVA_LANG_OBJECT; - } - String name = map.nameByType(longTypeName); - if (name != null && isIdentifier(name)) { - return new String[]{name}; - } - } - - Collection suggestions = new LinkedHashSet(); - - suggestNamesForCollectionInheritors(type, variableKind, suggestions, correctKeywords); - suggestNamesFromGenericParameters(type, variableKind, suggestions, correctKeywords); - - String typeName = normalizeTypeName(getTypeName(type)); - if (typeName != null) { - ContainerUtil.addAll(suggestions, getSuggestionsByName(typeName, variableKind, type instanceof PsiArrayType, correctKeywords)); - } - - return ArrayUtil.toStringArray(suggestions); - } - - private void suggestNamesFromGenericParameters(final PsiType type, - final VariableKind variableKind, - final Collection suggestions, boolean correctKeywords) { - if (!(type instanceof PsiClassType)) { - return; - } - StringBuilder fullNameBuilder = new StringBuilder(); - final PsiType[] parameters = ((PsiClassType)type).getParameters(); - for (PsiType parameter : parameters) { - if (parameter instanceof PsiClassType) { - final String typeName = normalizeTypeName(getTypeName(parameter)); - if (typeName != null) { - fullNameBuilder.append(typeName); - } - } - } - String baseName = normalizeTypeName(getTypeName(type)); - if (baseName != null) { - fullNameBuilder.append(baseName); - ContainerUtil.addAll(suggestions, getSuggestionsByName(fullNameBuilder.toString(), variableKind, false, correctKeywords)); - } - } - - private void suggestNamesForCollectionInheritors(final PsiType type, - final VariableKind variableKind, - Collection suggestions, boolean correctKeywords) { - PsiType componentType = PsiUtil.extractIterableTypeParameter(type, false); - if( componentType == null ) { - return; - } - String typeName = normalizeTypeName(getTypeName(componentType)); - if (typeName != null) { - ContainerUtil.addAll(suggestions, getSuggestionsByName(typeName, variableKind, true, correctKeywords)); - } - } - - @Nullable - private static String normalizeTypeName(String typeName) { - if( typeName == null ) - { - return null; - } - if (typeName.endsWith(IMPL_TYPNAME_SUFFIX) && typeName.length() > IMPL_TYPNAME_SUFFIX.length()) { - return typeName.substring(0, typeName.length() - IMPL_TYPNAME_SUFFIX.length()); - } - return typeName; - } - - @Nullable - private static String getTypeName(PsiType type) { - type = type.getDeepComponentType(); - if (type instanceof PsiClassType) { - final PsiClassType classType = (PsiClassType)type; - final String className = classType.getClassName(); - if (className != null) { - return className; - } - else { - final PsiClass aClass = classType.resolve(); - if (aClass instanceof PsiAnonymousClass) { - return ((PsiAnonymousClass)aClass).getBaseClassType().getClassName(); - } - else { - return null; - } - } - } - else { - if (type instanceof PsiPrimitiveType) { - return type.getPresentableText(); - } - else { - if (type instanceof PsiWildcardType) { - return getTypeName(((PsiWildcardType)type).getExtendsBound()); - } - else { - if (type instanceof PsiIntersectionType) { - return getTypeName(((PsiIntersectionType)type).getRepresentative()); - } - else { - if (type instanceof PsiCapturedWildcardType) { - return getTypeName(((PsiCapturedWildcardType)type).getWildcard()); - } - else { - if (type instanceof PsiDisjunctionType) { - return getTypeName(((PsiDisjunctionType)type).getLeastUpperBound()); - } - else { - LOG.error("Unknown type:" + type); - return null; - } - } - } - } - } - } - } - - @Nullable - private static String getLongTypeName(PsiType type) { - if (type instanceof PsiClassType) { - PsiClass aClass = ((PsiClassType)type).resolve(); - if( aClass == null ) - { - return null; - } - if (aClass instanceof PsiAnonymousClass) { - PsiClass baseClass = ((PsiAnonymousClass)aClass).getBaseClassType().resolve(); - if( baseClass == null ) - { - return null; - } - return baseClass.getQualifiedName(); - } - return aClass.getQualifiedName(); - } - else { - if (type instanceof PsiArrayType) { - return getLongTypeName(((PsiArrayType)type).getComponentType()) + "[]"; - } - else { - if (type instanceof PsiPrimitiveType) { - return type.getPresentableText(); - } - else { - if (type instanceof PsiWildcardType) { - final PsiType bound = ((PsiWildcardType)type).getBound(); - if (bound != null) { - return getLongTypeName(bound); - } - else { - return "java.lang.Object"; - } - } - else { - if (type instanceof PsiCapturedWildcardType) { - final PsiType bound = ((PsiCapturedWildcardType)type).getWildcard().getBound(); - if (bound != null) { - return getLongTypeName(bound); - } - else { - return "java.lang.Object"; - } - } - else { - if (type instanceof PsiIntersectionType) { - return getLongTypeName(((PsiIntersectionType)type).getRepresentative()); - } - else { - if (type instanceof PsiDisjunctionType) { - return getLongTypeName(((PsiDisjunctionType)type).getLeastUpperBound()); - } - else { - LOG.error("Unknown type:" + type); - return null; - } - } - } - } - } - } - } - } - - private static class NamesByExprInfo { - final String[] names; - final String propertyName; - - public NamesByExprInfo(String propertyName, String... names) { - this.names = names; - this.propertyName = propertyName; - } - } - - private NamesByExprInfo suggestVariableNameByExpression(PsiExpression expr, VariableKind variableKind, boolean correctKeywords) { - final NamesByExprInfo names1 = suggestVariableNameByExpressionOnly(expr, variableKind, correctKeywords); - final NamesByExprInfo names2 = suggestVariableNameByExpressionPlace(expr, variableKind, correctKeywords); - - PsiType type = expr.getType(); - final String[] names3; - if (type != null) { - names3 = suggestVariableNameByType(type, variableKind, correctKeywords); - } - else { - names3 = null; - } - - final LinkedHashSet names = new LinkedHashSet(); - final String[] fromLiterals = suggestVariableNameFromLiterals(expr, variableKind, correctKeywords); - if (fromLiterals != null) { - ContainerUtil.addAll(names, fromLiterals); - } - ContainerUtil.addAll(names, names1.names); - ContainerUtil.addAll(names, names2.names); - if (names3 != null) { - ContainerUtil.addAll(names, names3); - } - - String[] namesArray = ArrayUtil.toStringArray(names); - String propertyName = names1.propertyName != null ? names1.propertyName : names2.propertyName; - return new NamesByExprInfo(propertyName, namesArray); - } - - @Nullable - private String[] suggestVariableNameFromLiterals(PsiExpression expr, VariableKind variableKind, boolean correctKeywords) { - final PsiElement[] literals = PsiTreeUtil.collectElements(expr, new PsiElementFilter() { - @Override - public boolean isAccepted(PsiElement element) { - if (isStringPsiLiteral(element) && StringUtil.isJavaIdentifier(StringUtil.unquoteString(element.getText()))) { - final PsiElement exprList = element.getParent(); - if (exprList instanceof PsiExpressionList) { - final PsiElement call = exprList.getParent(); - if (call instanceof PsiNewExpression) { - return true; - } else if (call instanceof PsiMethodCallExpression) { - //TODO: exclude or not getA().getB("name").getC(); or getA(getB("name").getC()); It works fine for now in the most cases - return true; - } - } - } - return false; - } - }); - - if (literals.length == 1) { - final String text = StringUtil.unquoteString(literals[0].getText()); - return getSuggestionsByName(text, variableKind, expr.getType() instanceof PsiArrayType, correctKeywords); - } - return null; - } - - private NamesByExprInfo suggestVariableNameByExpressionOnly(PsiExpression expr, final VariableKind variableKind, boolean correctKeywords) { - if (expr instanceof PsiMethodCallExpression) { - PsiReferenceExpression methodExpr = ((PsiMethodCallExpression)expr).getMethodExpression(); - String methodName = methodExpr.getReferenceName(); - if (methodName != null) { - String[] words = NameUtil.nameToWords(methodName); - if (words.length > 0) { - final String firstWord = words[0]; - if (GET_PREFIX.equals(firstWord) - || IS_PREFIX.equals(firstWord) - || FIND_PREFIX.equals(firstWord) - || CREATE_PREFIX.equals(firstWord)) { - if (words.length > 1) { - final String propertyName = methodName.substring(firstWord.length()); - String[] names = getSuggestionsByName(propertyName, variableKind, false, correctKeywords); - final PsiExpression qualifierExpression = methodExpr.getQualifierExpression(); - if (qualifierExpression instanceof PsiReferenceExpression && ((PsiReferenceExpression)qualifierExpression).resolve() instanceof PsiVariable) { - names = ArrayUtil.append(names, StringUtil.sanitizeJavaIdentifier(changeIfNotIdentifier(qualifierExpression.getText() + StringUtil.capitalize(propertyName)))); - } - return new NamesByExprInfo(propertyName, names); - } - } - else if (words.length == 1) { - return new NamesByExprInfo(methodName, getSuggestionsByName(methodName, variableKind, false, correctKeywords)); - } - } - } - } - else if (expr instanceof PsiReferenceExpression) { - String propertyName = ((PsiReferenceExpression)expr).getReferenceName(); - PsiElement refElement = ((PsiReferenceExpression)expr).resolve(); - if (refElement instanceof PsiVariable) { - VariableKind refVariableKind = getVariableKind((PsiVariable)refElement); - propertyName = variableNameToPropertyName(propertyName, refVariableKind); - } - if (refElement != null && propertyName != null) { - String[] names = getSuggestionsByName(propertyName, variableKind, false, correctKeywords); - return new NamesByExprInfo(propertyName, names); - } - } - else if (expr instanceof PsiArrayAccessExpression) { - PsiExpression arrayExpr = ((PsiArrayAccessExpression)expr).getArrayExpression(); - if (arrayExpr instanceof PsiReferenceExpression) { - String arrayName = ((PsiReferenceExpression)arrayExpr).getReferenceName(); - PsiElement refElement = ((PsiReferenceExpression)arrayExpr).resolve(); - if (refElement instanceof PsiVariable) { - VariableKind refVariableKind = getVariableKind((PsiVariable)refElement); - arrayName = variableNameToPropertyName(arrayName, refVariableKind); - } - - if (arrayName != null) { - String name = StringUtil.unpluralize(arrayName); - if (name != null) { - String[] names = getSuggestionsByName(name, variableKind, false, correctKeywords); - return new NamesByExprInfo(name, names); - } - } - } - } - else if (expr instanceof PsiLiteralExpression && variableKind == VariableKind.STATIC_FINAL_FIELD) { - final PsiLiteralExpression literalExpression = (PsiLiteralExpression)expr; - final Object value = literalExpression.getValue(); - if (value instanceof String) { - final String stringValue = (String)value; - String[] names = getSuggestionsByValue(stringValue); - if (names.length > 0) { - return new NamesByExprInfo(null, constantValueToConstantName(names)); - } - } - } else if (expr instanceof PsiParenthesizedExpression) { - return suggestVariableNameByExpressionOnly(((PsiParenthesizedExpression)expr).getExpression(), variableKind, correctKeywords); - } else if (expr instanceof PsiTypeCastExpression) { - return suggestVariableNameByExpressionOnly(((PsiTypeCastExpression)expr).getOperand(), variableKind, correctKeywords); - } else if (expr instanceof PsiLiteralExpression) { - final String text = StringUtil.stripQuotesAroundValue(expr.getText()); - if (isIdentifier(text)) { - return new NamesByExprInfo(text, getSuggestionsByName(text, variableKind, false, correctKeywords)); - } - } - - return new NamesByExprInfo(null, ArrayUtil.EMPTY_STRING_ARRAY); - } - - private static String constantValueToConstantName(final String[] names) { - final StringBuilder result = new StringBuilder(); - for (int i = 0; i < names.length; i++) { - if (i > 0) result.append("_"); - result.append(names[i]); - } - return result.toString(); - } - - private static String[] getSuggestionsByValue(final String stringValue) { - List result = new ArrayList(); - StringBuffer currentWord = new StringBuffer(); - - boolean prevIsUpperCase = false; - - for (int i = 0; i < stringValue.length(); i++) { - final char c = stringValue.charAt(i); - if (Character.isUpperCase(c)) { - if (currentWord.length() > 0 && !prevIsUpperCase) { - result.add(currentWord.toString()); - currentWord = new StringBuffer(); - } - currentWord.append(c); - } else if (Character.isLowerCase(c)) { - currentWord.append(Character.toUpperCase(c)); - } else if (Character.isJavaIdentifierPart(c) && c != '_') { - if (Character.isJavaIdentifierStart(c) || currentWord.length() > 0 || !result.isEmpty()) { - currentWord.append(c); - } - } else { - if (currentWord.length() > 0) { - result.add(currentWord.toString()); - currentWord = new StringBuffer(); - } - } - - prevIsUpperCase = Character.isUpperCase(c); - } - - if (currentWord.length() > 0) { - result.add(currentWord.toString()); - } - return ArrayUtil.toStringArray(result); - } - - private NamesByExprInfo suggestVariableNameByExpressionPlace(PsiExpression expr, final VariableKind variableKind, boolean correctKeywords) { - if (expr.getParent() instanceof PsiExpressionList) { - PsiExpressionList list = (PsiExpressionList)expr.getParent(); - PsiElement listParent = list.getParent(); - PsiSubstitutor subst = PsiSubstitutor.EMPTY; - PsiMethod method = null; - if (listParent instanceof PsiMethodCallExpression) { - final JavaResolveResult resolveResult = ((PsiMethodCallExpression)listParent).getMethodExpression().advancedResolve(false); - method = (PsiMethod)resolveResult.getElement(); - subst = resolveResult.getSubstitutor(); - } - else { - if (listParent instanceof PsiAnonymousClass) { - listParent = listParent.getParent(); - } - if (listParent instanceof PsiNewExpression) { - method = ((PsiNewExpression)listParent).resolveConstructor(); - } - } - - if (method != null) { - final PsiElement navElement = method.getNavigationElement(); - if (navElement instanceof PsiMethod) { - method = (PsiMethod)navElement; - } - PsiExpression[] expressions = list.getExpressions(); - int index = -1; - for (int i = 0; i < expressions.length; i++) { - if (expressions[i] == expr) { - index = i; - break; - } - } - PsiParameter[] parms = method.getParameterList().getParameters(); - if (index < parms.length) { - String name = parms[index].getName(); - if (name != null && TypeConversionUtil.areTypesAssignmentCompatible(subst.substitute(parms[index].getType()), expr)) { - name = variableNameToPropertyName(name, VariableKind.PARAMETER); - String[] names = getSuggestionsByName(name, variableKind, false, correctKeywords); - if (expressions.length == 1) { - final String methodName = method.getName(); - String[] words = NameUtil.nameToWords(methodName); - if (words.length > 0) { - final String firstWord = words[0]; - if (SET_PREFIX.equals(firstWord)) { - final String propertyName = methodName.substring(firstWord.length()); - final String[] setterNames = getSuggestionsByName(propertyName, variableKind, false, correctKeywords); - names = ArrayUtil.mergeArrays(names, setterNames); - } - } - } - return new NamesByExprInfo(name, names); - } - } - } - } - else if (expr.getParent() instanceof PsiAssignmentExpression && variableKind == VariableKind.PARAMETER) { - final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)expr.getParent(); - if (expr == assignmentExpression.getRExpression()) { - final PsiExpression leftExpression = assignmentExpression.getLExpression(); - if (leftExpression instanceof PsiReferenceExpression && ((PsiReferenceExpression) leftExpression).getQualifier() == null) { - String name = leftExpression.getText(); - if (name != null) { - final PsiElement resolve = ((PsiReferenceExpression)leftExpression).resolve(); - if (resolve instanceof PsiVariable) { - name = variableNameToPropertyName(name, getVariableKind((PsiVariable)resolve)); - } - String[] names = getSuggestionsByName(name, variableKind, false, correctKeywords); - return new NamesByExprInfo(name, names); - } - } - } - } - - return new NamesByExprInfo(null, ArrayUtil.EMPTY_STRING_ARRAY); - } - - @Override - public String variableNameToPropertyName(String name, VariableKind variableKind) { - if (variableKind == VariableKind.STATIC_FINAL_FIELD) { - StringBuilder buffer = new StringBuilder(); - for (int i = 0; i < name.length(); i++) { - char c = name.charAt(i); - if (c != '_') { - if( Character.isLowerCase( c ) ) - { - return variableNameToPropertyNameInner( name, variableKind ); - } - - buffer.append(Character.toLowerCase(c)); - continue; - } - i++; - if (i < name.length()) { - c = name.charAt(i); - buffer.append(c); - } - } - return buffer.toString(); - } - - return variableNameToPropertyNameInner(name, variableKind); - } - - private String variableNameToPropertyNameInner(String name, VariableKind variableKind) { - String prefix = getPrefixByVariableKind(variableKind); - String suffix = getSuffixByVariableKind(variableKind); - boolean doDecapitalize = false; - - if (name.startsWith(prefix) && name.length() > prefix.length()) { - name = name.substring(prefix.length()); - doDecapitalize = true; - } - - if (name.endsWith(suffix) && name.length() > suffix.length()) { - name = name.substring(0, name.length() - suffix.length()); - doDecapitalize = true; - } - - if (name.startsWith(IS_PREFIX) && name.length() > IS_PREFIX.length() && Character.isUpperCase(name.charAt(IS_PREFIX.length()))) { - name = name.substring(IS_PREFIX.length()); - doDecapitalize = true; - } - - if (doDecapitalize) { - name = Introspector.decapitalize(name); - } - - return name; - } - - @Override - public String propertyNameToVariableName(String propertyName, VariableKind variableKind) { - if (variableKind == VariableKind.STATIC_FINAL_FIELD) { - String[] words = NameUtil.nameToWords(propertyName); - StringBuilder buffer = new StringBuilder(); - for (int i = 0; i < words.length; i++) { - String word = words[i]; - if (i > 0) { - buffer.append("_"); - } - buffer.append(StringUtilRt.toUpperCase(word)); - } - return buffer.toString(); - } - - String prefix = getPrefixByVariableKind(variableKind); - String name = propertyName; - if (name.length() > 0 && prefix.length() > 0 && !StringUtil.endsWithChar(prefix, '_')) { - name = Character.toUpperCase(name.charAt(0)) + name.substring(1); - } - name = prefix + name + getSuffixByVariableKind(variableKind); - name = changeIfNotIdentifier(name); - return name; - } - - private String[] getSuggestionsByName(String name, VariableKind variableKind, boolean isArray, boolean correctKeywords) { - boolean upperCaseStyle = variableKind == VariableKind.STATIC_FINAL_FIELD; - boolean preferLongerNames = getSettings().PREFER_LONGER_NAMES; - String prefix = getPrefixByVariableKind(variableKind); - String suffix = getSuffixByVariableKind(variableKind); - - List answer = new ArrayList(); - for (String suggestion : NameUtil.getSuggestionsByName(name, prefix, suffix, upperCaseStyle, preferLongerNames, isArray)) { - answer.add(correctKeywords ? changeIfNotIdentifier(suggestion) : suggestion); - } - - return ArrayUtil.toStringArray(answer); - } - - @Override - public String suggestUniqueVariableName(String baseName, PsiElement place, boolean lookForward) { - int index = 0; - PsiElement scope = PsiTreeUtil.getNonStrictParentOfType(place, PsiStatement.class, PsiCodeBlock.class, PsiMethod.class); - NextName: - while (true) { - String name = baseName; - if (index > 0) { - name += index; - } - index++; - if (PsiUtil.isVariableNameUnique(name, place)) { - if (lookForward) { - final String name1 = name; - PsiElement run = scope; - while (run != null) { - class CancelException extends RuntimeException { - } - try { - run.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitAnonymousClass(final PsiAnonymousClass aClass) { - } - - @Override public void visitVariable(PsiVariable variable) { - if (name1.equals(variable.getName())) { - throw new CancelException(); - } - } - }); - } - catch (CancelException e) { - continue NextName; - } - run = run.getNextSibling(); - if (scope instanceof PsiMethod) {//do not check next member for param name conflict - break; - } - } - - } - return name; - } - } - } - - @Override - @NotNull - public SuggestedNameInfo suggestUniqueVariableName(@NotNull final SuggestedNameInfo baseNameInfo, - PsiElement place, - boolean ignorePlaceName, - boolean lookForward) { - final String[] names = baseNameInfo.names; - final LinkedHashSet uniqueNames = new LinkedHashSet(names.length); - for (String name : names) { - if (ignorePlaceName && place instanceof PsiNamedElement) { - final String placeName = ((PsiNamedElement)place).getName(); - if (Comparing.strEqual(placeName, name)) { - uniqueNames.add(name); - continue; - } - } - uniqueNames.add(suggestUniqueVariableName(name, place, lookForward)); - } - - return new SuggestedNameInfo(ArrayUtil.toStringArray(uniqueNames)) { - @Override - public void nameChoosen(String name) { - baseNameInfo.nameChoosen(name); - } - }; - } - - private static void sortVariableNameSuggestions(String[] names, - final VariableKind variableKind, - @Nullable final String propertyName, - @Nullable final PsiType type) { - if( names.length <= 1 ) { - return; - } - - if (LOG.isDebugEnabled()) { - LOG.debug("sorting names:" + variableKind); - if (propertyName != null) { - LOG.debug("propertyName:" + propertyName); - } - if (type != null) { - LOG.debug("type:" + type); - } - for (String name : names) { - int count = JavaStatisticsManager.getVariableNameUseCount(name, variableKind, propertyName, type); - LOG.debug(name + " : " + count); - } - } - - Comparator comparator = new Comparator() { - @Override - public int compare(String s1, String s2) { - int count1 = JavaStatisticsManager.getVariableNameUseCount(s1, variableKind, propertyName, type); - int count2 = JavaStatisticsManager.getVariableNameUseCount(s2, variableKind, propertyName, type); - return count2 - count1; - } - }; - Arrays.sort(names, comparator); - } - - @Override - @NotNull - public String getPrefixByVariableKind(VariableKind variableKind) { - String prefix = ""; - switch (variableKind) { - case FIELD: - prefix = getSettings().FIELD_NAME_PREFIX; - break; - case STATIC_FIELD: - prefix = getSettings().STATIC_FIELD_NAME_PREFIX; - break; - case PARAMETER: - prefix = getSettings().PARAMETER_NAME_PREFIX; - break; - case LOCAL_VARIABLE: - prefix = getSettings().LOCAL_VARIABLE_NAME_PREFIX; - break; - case STATIC_FINAL_FIELD: - prefix = ""; - break; - default: - LOG.assertTrue(false); - break; - } - if (prefix == null) { - prefix = ""; - } - return prefix; - } - - @Override - @NotNull - public String getSuffixByVariableKind(VariableKind variableKind) { - String suffix = ""; - switch (variableKind) { - case FIELD: - suffix = getSettings().FIELD_NAME_SUFFIX; - break; - case STATIC_FIELD: - suffix = getSettings().STATIC_FIELD_NAME_SUFFIX; - break; - case PARAMETER: - suffix = getSettings().PARAMETER_NAME_SUFFIX; - break; - case LOCAL_VARIABLE: - suffix = getSettings().LOCAL_VARIABLE_NAME_SUFFIX; - break; - case STATIC_FINAL_FIELD: - suffix = ""; - break; - default: - LOG.assertTrue(false); - break; - } - if (suffix == null) { - suffix = ""; - } - return suffix; - } - - @Nullable - private CodeStyleSettings.TypeToNameMap getMapByVariableKind(VariableKind variableKind) { - if (variableKind == VariableKind.FIELD) return getSettings().FIELD_TYPE_TO_NAME; - if (variableKind == VariableKind.STATIC_FIELD) return getSettings().STATIC_FIELD_TYPE_TO_NAME; - if (variableKind == VariableKind.PARAMETER) return getSettings().PARAMETER_TYPE_TO_NAME; - if (variableKind == VariableKind.LOCAL_VARIABLE) return getSettings().LOCAL_VARIABLE_TYPE_TO_NAME; - return null; - } - - @NonNls - private String changeIfNotIdentifier(String name) { - if (!isIdentifier(name)) { - return StringUtil.fixVariableNameDerivedFromPropertyName(name); - } - return name; - } - - private boolean isIdentifier(String name) { - return JavaPsiFacade.getInstance(myProject).getNameHelper().isIdentifier(name, LanguageLevel.HIGHEST); - } - - private CodeStyleSettings getSettings() { - return CodeStyleSettingsManager.getSettings(myProject); - } - - public static boolean isStringPsiLiteral(PsiElement element) { - if (element instanceof PsiLiteralExpression) { - final String text = element.getText(); - return text.length() > 1 && StringUtil.isQuotedString(text); - } - return false; - } -} +/* + * Copyright 2000-2011 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. + */ + +/* + * @author max + */ +package com.intellij.psi.impl.source.codeStyle; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.util.text.StringUtilRt; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.*; +import com.intellij.psi.impl.CheckUtil; +import com.intellij.psi.impl.source.SourceTreeToPsiMap; +import com.intellij.psi.impl.source.jsp.jspJava.JspxImportStatement; +import com.intellij.psi.impl.source.tree.TreeElement; +import com.intellij.psi.statistics.JavaStatisticsManager; +import com.intellij.psi.util.*; +import com.intellij.util.ArrayUtil; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.ContainerUtil; +import gnu.trove.THashSet; +import gnu.trove.TObjectHashingStrategy; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.beans.Introspector; +import java.util.*; + +public class JavaCodeStyleManagerImpl extends JavaCodeStyleManager { + private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.codeStyle.JavaCodeStyleManagerImpl"); + + @NonNls private static final String IMPL_TYPNAME_SUFFIX = "Impl"; + @NonNls private static final String GET_PREFIX = "get"; + @NonNls private static final String IS_PREFIX = "is"; + @NonNls private static final String FIND_PREFIX = "find"; + @NonNls private static final String CREATE_PREFIX = "create"; + @NonNls private static final String WITH_PREFIX = "with"; + @NonNls private static final String SET_PREFIX = "set"; + + private final Project myProject; + + public JavaCodeStyleManagerImpl(final Project project) { + myProject = project; + } + + @Override + public PsiElement shortenClassReferences(@NotNull PsiElement element) throws IncorrectOperationException { + return shortenClassReferences(element, 0); + } + + @Override + public PsiElement shortenClassReferences(@NotNull PsiElement element, int flags) throws IncorrectOperationException { + CheckUtil.checkWritable(element); + if (!SourceTreeToPsiMap.hasTreeElement(element)) return element; + + return SourceTreeToPsiMap.treeElementToPsi( + new ReferenceAdjuster(myProject).process((TreeElement)element.getNode(), (flags & DO_NOT_ADD_IMPORTS) == 0, + (flags & UNCOMPLETE_CODE) != 0)); + } + + @Override + public void shortenClassReferences(@NotNull PsiElement element, int startOffset, int endOffset) + throws IncorrectOperationException { + CheckUtil.checkWritable(element); + if (SourceTreeToPsiMap.hasTreeElement(element)) { + new ReferenceAdjuster(myProject).processRange((TreeElement)element.getNode(), startOffset, endOffset); + } + } + + @Override + public PsiElement qualifyClassReferences(@NotNull PsiElement element) { + return SourceTreeToPsiMap.treeElementToPsi(new ReferenceAdjuster(true, true).process((TreeElement)element.getNode(), false, false)); + } + + @Override + public void optimizeImports(@NotNull PsiFile file) throws IncorrectOperationException { + CheckUtil.checkWritable(file); + if (file instanceof PsiJavaFile) { + PsiImportList newList = prepareOptimizeImportsResult((PsiJavaFile)file); + if (newList != null) { + final PsiImportList importList = ((PsiJavaFile)file).getImportList(); + if (importList != null) { + importList.replace(newList); + } + } + } + } + + @Override + public PsiImportList prepareOptimizeImportsResult(@NotNull PsiJavaFile file) { + return new ImportHelper(getSettings()).prepareOptimizeImportsResult(file); + } + + @Override + public boolean addImport(@NotNull PsiJavaFile file, @NotNull PsiClass refClass) { + return new ImportHelper(getSettings()).addImport(file, refClass); + } + + @Override + public void removeRedundantImports(@NotNull final PsiJavaFile file) throws IncorrectOperationException { + final Collection redundants = findRedundantImports(file); + if (redundants == null) return; + + for (final PsiImportStatementBase importStatement : redundants) { + final PsiJavaCodeReferenceElement ref = importStatement.getImportReference(); + //Do not remove non-resolving refs + if (ref == null || ref.resolve() == null) { + continue; + } + + importStatement.delete(); + } + } + + @Override + @Nullable + public Collection findRedundantImports(final PsiJavaFile file) { + final PsiImportList importList = file.getImportList(); + if (importList == null) return null; + final PsiImportStatementBase[] imports = importList.getAllImportStatements(); + if( imports.length == 0 ) return null; + + Set allImports = new THashSet(Arrays.asList(imports)); + final Collection redundants; + if (JspPsiUtil.isInJspFile(file)) { + // remove only duplicate imports + redundants = new THashSet(TObjectHashingStrategy.IDENTITY); + ContainerUtil.addAll(redundants, imports); + redundants.removeAll(allImports); + for (PsiImportStatementBase importStatement : imports) { + if (importStatement instanceof JspxImportStatement && ((JspxImportStatement)importStatement).isForeignFileImport()) { + redundants.remove(importStatement); + } + } + } + else { + redundants = allImports; + final List roots = file.getViewProvider().getAllFiles(); + for (PsiElement root : roots) { + root.accept(new JavaRecursiveElementWalkingVisitor() { + @Override public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { + if (!reference.isQualified()) { + final JavaResolveResult resolveResult = reference.advancedResolve(false); + if (!inTheSamePackage(file, resolveResult.getElement())) { + final PsiElement resolveScope = resolveResult.getCurrentFileResolveScope(); + if (resolveScope instanceof PsiImportStatementBase) { + final PsiImportStatementBase importStatementBase = (PsiImportStatementBase)resolveScope; + redundants.remove(importStatementBase); + } + } + } + super.visitReferenceElement(reference); + } + + private boolean inTheSamePackage(PsiJavaFile file, PsiElement element) { + if (element instanceof PsiClass && ((PsiClass)element).getContainingClass() == null) { + final PsiFile containingFile = element.getContainingFile(); + if (containingFile instanceof PsiJavaFile) { + return Comparing.strEqual(file.getPackageName(), ((PsiJavaFile)containingFile).getPackageName()); + } + } + return false; + } + }); + } + } + return redundants; + } + + @Override + public int findEntryIndex(@NotNull PsiImportStatementBase statement) { + return new ImportHelper(getSettings()).findEntryIndex(statement); + } + + @Override + public SuggestedNameInfo suggestVariableName(@NotNull final VariableKind kind, + @Nullable final String propertyName, + @Nullable final PsiExpression expr, + @Nullable PsiType type, + final boolean correctKeywords) { + LinkedHashSet names = new LinkedHashSet(); + + if (expr != null && type == null) { + type = expr.getType(); + } + + if (propertyName != null) { + String[] namesByName = getSuggestionsByName(propertyName, kind, false, correctKeywords); + sortVariableNameSuggestions(namesByName, kind, propertyName, null); + ContainerUtil.addAll(names, namesByName); + } + + final NamesByExprInfo namesByExpr; + if (expr != null) { + namesByExpr = suggestVariableNameByExpression(expr, kind, correctKeywords); + if (namesByExpr.propertyName != null) { + sortVariableNameSuggestions(namesByExpr.names, kind, namesByExpr.propertyName, null); + } + ContainerUtil.addAll(names, namesByExpr.names); + } + else { + namesByExpr = null; + } + + if (type != null) { + String[] namesByType = suggestVariableNameByType(type, kind, correctKeywords); + sortVariableNameSuggestions(namesByType, kind, null, type); + ContainerUtil.addAll(names, namesByType); + } + + final String _propertyName; + if (propertyName != null) { + _propertyName = propertyName; + } + else { + _propertyName = namesByExpr != null ? namesByExpr.propertyName : null; + } + + addNamesFromStatistics(names, kind, _propertyName, type); + + String[] namesArray = ArrayUtil.toStringArray(names); + sortVariableNameSuggestions(namesArray, kind, _propertyName, type); + + final PsiType _type = type; + return new SuggestedNameInfo(namesArray) { + @Override + public void nameChoosen(String name) { + if (_propertyName != null || _type != null && _type.isValid()) { + JavaStatisticsManager.incVariableNameUseCount(name, kind, _propertyName, _type); + } + } + }; + } + + private static void addNamesFromStatistics(Set names, VariableKind variableKind, @Nullable String propertyName, @Nullable PsiType type) { + String[] allNames = JavaStatisticsManager.getAllVariableNamesUsed(variableKind, propertyName, type); + + int maxFrequency = 0; + for (String name : allNames) { + int count = JavaStatisticsManager.getVariableNameUseCount(name, variableKind, propertyName, type); + maxFrequency = Math.max(maxFrequency, count); + } + + int frequencyLimit = Math.max(5, maxFrequency / 2); + + for (String name : allNames) { + if( names.contains( name ) ) + { + continue; + } + int count = JavaStatisticsManager.getVariableNameUseCount(name, variableKind, propertyName, type); + if (LOG.isDebugEnabled()) { + LOG.debug("new name:" + name + " count:" + count); + LOG.debug("frequencyLimit:" + frequencyLimit); + } + if (count >= frequencyLimit) { + names.add(name); + } + } + + if (propertyName != null && type != null) { + addNamesFromStatistics(names, variableKind, propertyName, null); + addNamesFromStatistics(names, variableKind, null, type); + } + } + + private String[] suggestVariableNameByType(PsiType type, final VariableKind variableKind, boolean correctKeywords) { + String longTypeName = getLongTypeName(type); + CodeStyleSettings.TypeToNameMap map = getMapByVariableKind(variableKind); + if (map != null && longTypeName != null) { + if (type.equals(PsiType.NULL)) { + longTypeName = CommonClassNames.JAVA_LANG_OBJECT; + } + String name = map.nameByType(longTypeName); + if (name != null && isIdentifier(name)) { + return new String[]{name}; + } + } + + Collection suggestions = new LinkedHashSet(); + + suggestNamesForCollectionInheritors(type, variableKind, suggestions, correctKeywords); + suggestNamesFromGenericParameters(type, variableKind, suggestions, correctKeywords); + + String typeName = normalizeTypeName(getTypeName(type)); + if (typeName != null) { + ContainerUtil.addAll(suggestions, getSuggestionsByName(typeName, variableKind, type instanceof PsiArrayType, correctKeywords)); + } + + return ArrayUtil.toStringArray(suggestions); + } + + private void suggestNamesFromGenericParameters(final PsiType type, + final VariableKind variableKind, + final Collection suggestions, boolean correctKeywords) { + if (!(type instanceof PsiClassType)) { + return; + } + StringBuilder fullNameBuilder = new StringBuilder(); + final PsiType[] parameters = ((PsiClassType)type).getParameters(); + for (PsiType parameter : parameters) { + if (parameter instanceof PsiClassType) { + final String typeName = normalizeTypeName(getTypeName(parameter)); + if (typeName != null) { + fullNameBuilder.append(typeName); + } + } + } + String baseName = normalizeTypeName(getTypeName(type)); + if (baseName != null) { + fullNameBuilder.append(baseName); + ContainerUtil.addAll(suggestions, getSuggestionsByName(fullNameBuilder.toString(), variableKind, false, correctKeywords)); + } + } + + private void suggestNamesForCollectionInheritors(final PsiType type, + final VariableKind variableKind, + Collection suggestions, boolean correctKeywords) { + PsiType componentType = PsiUtil.extractIterableTypeParameter(type, false); + if( componentType == null ) { + return; + } + String typeName = normalizeTypeName(getTypeName(componentType)); + if (typeName != null) { + ContainerUtil.addAll(suggestions, getSuggestionsByName(typeName, variableKind, true, correctKeywords)); + } + } + + @Nullable + private static String normalizeTypeName(String typeName) { + if( typeName == null ) + { + return null; + } + if (typeName.endsWith(IMPL_TYPNAME_SUFFIX) && typeName.length() > IMPL_TYPNAME_SUFFIX.length()) { + return typeName.substring(0, typeName.length() - IMPL_TYPNAME_SUFFIX.length()); + } + return typeName; + } + + @Nullable + private static String getTypeName(PsiType type) { + type = type.getDeepComponentType(); + if (type instanceof PsiClassType) { + final PsiClassType classType = (PsiClassType)type; + final String className = classType.getClassName(); + if (className != null) { + return className; + } + else { + final PsiClass aClass = classType.resolve(); + if (aClass instanceof PsiAnonymousClass) { + return ((PsiAnonymousClass)aClass).getBaseClassType().getClassName(); + } + else { + return null; + } + } + } + else { + if (type instanceof PsiPrimitiveType) { + return type.getPresentableText(); + } + else { + if (type instanceof PsiWildcardType) { + return getTypeName(((PsiWildcardType)type).getExtendsBound()); + } + else { + if (type instanceof PsiIntersectionType) { + return getTypeName(((PsiIntersectionType)type).getRepresentative()); + } + else { + if (type instanceof PsiCapturedWildcardType) { + return getTypeName(((PsiCapturedWildcardType)type).getWildcard()); + } + else { + if (type instanceof PsiDisjunctionType) { + return getTypeName(((PsiDisjunctionType)type).getLeastUpperBound()); + } + else { + LOG.error("Unknown type:" + type); + return null; + } + } + } + } + } + } + } + + @Nullable + private static String getLongTypeName(PsiType type) { + if (type instanceof PsiClassType) { + PsiClass aClass = ((PsiClassType)type).resolve(); + if( aClass == null ) + { + return null; + } + if (aClass instanceof PsiAnonymousClass) { + PsiClass baseClass = ((PsiAnonymousClass)aClass).getBaseClassType().resolve(); + if( baseClass == null ) + { + return null; + } + return baseClass.getQualifiedName(); + } + return aClass.getQualifiedName(); + } + else { + if (type instanceof PsiArrayType) { + return getLongTypeName(((PsiArrayType)type).getComponentType()) + "[]"; + } + else { + if (type instanceof PsiPrimitiveType) { + return type.getPresentableText(); + } + else { + if (type instanceof PsiWildcardType) { + final PsiType bound = ((PsiWildcardType)type).getBound(); + if (bound != null) { + return getLongTypeName(bound); + } + else { + return "java.lang.Object"; + } + } + else { + if (type instanceof PsiCapturedWildcardType) { + final PsiType bound = ((PsiCapturedWildcardType)type).getWildcard().getBound(); + if (bound != null) { + return getLongTypeName(bound); + } + else { + return "java.lang.Object"; + } + } + else { + if (type instanceof PsiIntersectionType) { + return getLongTypeName(((PsiIntersectionType)type).getRepresentative()); + } + else { + if (type instanceof PsiDisjunctionType) { + return getLongTypeName(((PsiDisjunctionType)type).getLeastUpperBound()); + } + else { + LOG.error("Unknown type:" + type); + return null; + } + } + } + } + } + } + } + } + + private static class NamesByExprInfo { + final String[] names; + final String propertyName; + + public NamesByExprInfo(String propertyName, String... names) { + this.names = names; + this.propertyName = propertyName; + } + } + + private NamesByExprInfo suggestVariableNameByExpression(PsiExpression expr, VariableKind variableKind, boolean correctKeywords) { + final NamesByExprInfo names1 = suggestVariableNameByExpressionOnly(expr, variableKind, correctKeywords); + final NamesByExprInfo names2 = suggestVariableNameByExpressionPlace(expr, variableKind, correctKeywords); + + PsiType type = expr.getType(); + final String[] names3; + if (type != null) { + names3 = suggestVariableNameByType(type, variableKind, correctKeywords); + } + else { + names3 = null; + } + + final LinkedHashSet names = new LinkedHashSet(); + final String[] fromLiterals = suggestVariableNameFromLiterals(expr, variableKind, correctKeywords); + if (fromLiterals != null) { + ContainerUtil.addAll(names, fromLiterals); + } + ContainerUtil.addAll(names, names1.names); + ContainerUtil.addAll(names, names2.names); + if (names3 != null) { + ContainerUtil.addAll(names, names3); + } + + String[] namesArray = ArrayUtil.toStringArray(names); + String propertyName = names1.propertyName != null ? names1.propertyName : names2.propertyName; + return new NamesByExprInfo(propertyName, namesArray); + } + + @Nullable + private String[] suggestVariableNameFromLiterals(PsiExpression expr, VariableKind variableKind, boolean correctKeywords) { + final PsiElement[] literals = PsiTreeUtil.collectElements(expr, new PsiElementFilter() { + @Override + public boolean isAccepted(PsiElement element) { + if (isStringPsiLiteral(element) && StringUtil.isJavaIdentifier(StringUtil.unquoteString(element.getText()))) { + final PsiElement exprList = element.getParent(); + if (exprList instanceof PsiExpressionList) { + final PsiElement call = exprList.getParent(); + if (call instanceof PsiNewExpression) { + return true; + } else if (call instanceof PsiMethodCallExpression) { + //TODO: exclude or not getA().getB("name").getC(); or getA(getB("name").getC()); It works fine for now in the most cases + return true; + } + } + } + return false; + } + }); + + if (literals.length == 1) { + final String text = StringUtil.unquoteString(literals[0].getText()); + return getSuggestionsByName(text, variableKind, expr.getType() instanceof PsiArrayType, correctKeywords); + } + return null; + } + + private NamesByExprInfo suggestVariableNameByExpressionOnly(PsiExpression expr, final VariableKind variableKind, boolean correctKeywords) { + if (expr instanceof PsiMethodCallExpression) { + PsiReferenceExpression methodExpr = ((PsiMethodCallExpression)expr).getMethodExpression(); + String methodName = methodExpr.getReferenceName(); + if (methodName != null) { + String[] words = NameUtil.nameToWords(methodName); + if (words.length > 0) { + final String firstWord = words[0]; + if (GET_PREFIX.equals(firstWord) + || IS_PREFIX.equals(firstWord) + || FIND_PREFIX.equals(firstWord) + || CREATE_PREFIX.equals(firstWord)) { + if (words.length > 1) { + final String propertyName = methodName.substring(firstWord.length()); + String[] names = getSuggestionsByName(propertyName, variableKind, false, correctKeywords); + final PsiExpression qualifierExpression = methodExpr.getQualifierExpression(); + if (qualifierExpression instanceof PsiReferenceExpression && ((PsiReferenceExpression)qualifierExpression).resolve() instanceof PsiVariable) { + names = ArrayUtil.append(names, StringUtil.sanitizeJavaIdentifier(changeIfNotIdentifier(qualifierExpression.getText() + StringUtil.capitalize(propertyName)))); + } + return new NamesByExprInfo(propertyName, names); + } + } + else if (words.length == 1) { + return new NamesByExprInfo(methodName, getSuggestionsByName(methodName, variableKind, false, correctKeywords)); + } + } + } + } + else if (expr instanceof PsiReferenceExpression) { + String propertyName = ((PsiReferenceExpression)expr).getReferenceName(); + PsiElement refElement = ((PsiReferenceExpression)expr).resolve(); + if (refElement instanceof PsiVariable) { + VariableKind refVariableKind = getVariableKind((PsiVariable)refElement); + propertyName = variableNameToPropertyName(propertyName, refVariableKind); + } + if (refElement != null && propertyName != null) { + String[] names = getSuggestionsByName(propertyName, variableKind, false, correctKeywords); + return new NamesByExprInfo(propertyName, names); + } + } + else if (expr instanceof PsiArrayAccessExpression) { + PsiExpression arrayExpr = ((PsiArrayAccessExpression)expr).getArrayExpression(); + if (arrayExpr instanceof PsiReferenceExpression) { + String arrayName = ((PsiReferenceExpression)arrayExpr).getReferenceName(); + PsiElement refElement = ((PsiReferenceExpression)arrayExpr).resolve(); + if (refElement instanceof PsiVariable) { + VariableKind refVariableKind = getVariableKind((PsiVariable)refElement); + arrayName = variableNameToPropertyName(arrayName, refVariableKind); + } + + if (arrayName != null) { + String name = StringUtil.unpluralize(arrayName); + if (name != null) { + String[] names = getSuggestionsByName(name, variableKind, false, correctKeywords); + return new NamesByExprInfo(name, names); + } + } + } + } + else if (expr instanceof PsiLiteralExpression && variableKind == VariableKind.STATIC_FINAL_FIELD) { + final PsiLiteralExpression literalExpression = (PsiLiteralExpression)expr; + final Object value = literalExpression.getValue(); + if (value instanceof String) { + final String stringValue = (String)value; + String[] names = getSuggestionsByValue(stringValue); + if (names.length > 0) { + return new NamesByExprInfo(null, constantValueToConstantName(names)); + } + } + } else if (expr instanceof PsiParenthesizedExpression) { + return suggestVariableNameByExpressionOnly(((PsiParenthesizedExpression)expr).getExpression(), variableKind, correctKeywords); + } else if (expr instanceof PsiTypeCastExpression) { + return suggestVariableNameByExpressionOnly(((PsiTypeCastExpression)expr).getOperand(), variableKind, correctKeywords); + } else if (expr instanceof PsiLiteralExpression) { + final String text = StringUtil.stripQuotesAroundValue(expr.getText()); + if (isIdentifier(text)) { + return new NamesByExprInfo(text, getSuggestionsByName(text, variableKind, false, correctKeywords)); + } + } + + return new NamesByExprInfo(null, ArrayUtil.EMPTY_STRING_ARRAY); + } + + private static String constantValueToConstantName(final String[] names) { + final StringBuilder result = new StringBuilder(); + for (int i = 0; i < names.length; i++) { + if (i > 0) result.append("_"); + result.append(names[i]); + } + return result.toString(); + } + + private static String[] getSuggestionsByValue(final String stringValue) { + List result = new ArrayList(); + StringBuffer currentWord = new StringBuffer(); + + boolean prevIsUpperCase = false; + + for (int i = 0; i < stringValue.length(); i++) { + final char c = stringValue.charAt(i); + if (Character.isUpperCase(c)) { + if (currentWord.length() > 0 && !prevIsUpperCase) { + result.add(currentWord.toString()); + currentWord = new StringBuffer(); + } + currentWord.append(c); + } else if (Character.isLowerCase(c)) { + currentWord.append(Character.toUpperCase(c)); + } else if (Character.isJavaIdentifierPart(c) && c != '_') { + if (Character.isJavaIdentifierStart(c) || currentWord.length() > 0 || !result.isEmpty()) { + currentWord.append(c); + } + } else { + if (currentWord.length() > 0) { + result.add(currentWord.toString()); + currentWord = new StringBuffer(); + } + } + + prevIsUpperCase = Character.isUpperCase(c); + } + + if (currentWord.length() > 0) { + result.add(currentWord.toString()); + } + return ArrayUtil.toStringArray(result); + } + + private NamesByExprInfo suggestVariableNameByExpressionPlace(PsiExpression expr, final VariableKind variableKind, boolean correctKeywords) { + if (expr.getParent() instanceof PsiExpressionList) { + PsiExpressionList list = (PsiExpressionList)expr.getParent(); + PsiElement listParent = list.getParent(); + PsiSubstitutor subst = PsiSubstitutor.EMPTY; + PsiMethod method = null; + if (listParent instanceof PsiMethodCallExpression) { + final JavaResolveResult resolveResult = ((PsiMethodCallExpression)listParent).getMethodExpression().advancedResolve(false); + method = (PsiMethod)resolveResult.getElement(); + subst = resolveResult.getSubstitutor(); + } + else { + if (listParent instanceof PsiAnonymousClass) { + listParent = listParent.getParent(); + } + if (listParent instanceof PsiNewExpression) { + method = ((PsiNewExpression)listParent).resolveConstructor(); + } + } + + if (method != null) { + final PsiElement navElement = method.getNavigationElement(); + if (navElement instanceof PsiMethod) { + method = (PsiMethod)navElement; + } + PsiExpression[] expressions = list.getExpressions(); + int index = -1; + for (int i = 0; i < expressions.length; i++) { + if (expressions[i] == expr) { + index = i; + break; + } + } + PsiParameter[] parms = method.getParameterList().getParameters(); + if (index < parms.length) { + String name = parms[index].getName(); + if (name != null && TypeConversionUtil.areTypesAssignmentCompatible(subst.substitute(parms[index].getType()), expr)) { + name = variableNameToPropertyName(name, VariableKind.PARAMETER); + String[] names = getSuggestionsByName(name, variableKind, false, correctKeywords); + if (expressions.length == 1) { + final String methodName = method.getName(); + String[] words = NameUtil.nameToWords(methodName); + if (words.length > 0) { + final String firstWord = words[0]; + if (SET_PREFIX.equals(firstWord)) { + final String propertyName = methodName.substring(firstWord.length()); + final String[] setterNames = getSuggestionsByName(propertyName, variableKind, false, correctKeywords); + names = ArrayUtil.mergeArrays(names, setterNames); + } + } + } + return new NamesByExprInfo(name, names); + } + } + } + } + else if (expr.getParent() instanceof PsiAssignmentExpression && variableKind == VariableKind.PARAMETER) { + final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)expr.getParent(); + if (expr == assignmentExpression.getRExpression()) { + final PsiExpression leftExpression = assignmentExpression.getLExpression(); + if (leftExpression instanceof PsiReferenceExpression && ((PsiReferenceExpression) leftExpression).getQualifier() == null) { + String name = leftExpression.getText(); + if (name != null) { + final PsiElement resolve = ((PsiReferenceExpression)leftExpression).resolve(); + if (resolve instanceof PsiVariable) { + name = variableNameToPropertyName(name, getVariableKind((PsiVariable)resolve)); + } + String[] names = getSuggestionsByName(name, variableKind, false, correctKeywords); + return new NamesByExprInfo(name, names); + } + } + } + } + + return new NamesByExprInfo(null, ArrayUtil.EMPTY_STRING_ARRAY); + } + + @Override + public String variableNameToPropertyName(String name, VariableKind variableKind) { + if (variableKind == VariableKind.STATIC_FINAL_FIELD) { + StringBuilder buffer = new StringBuilder(); + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + if (c != '_') { + if( Character.isLowerCase( c ) ) + { + return variableNameToPropertyNameInner( name, variableKind ); + } + + buffer.append(Character.toLowerCase(c)); + continue; + } + i++; + if (i < name.length()) { + c = name.charAt(i); + buffer.append(c); + } + } + return buffer.toString(); + } + + return variableNameToPropertyNameInner(name, variableKind); + } + + private String variableNameToPropertyNameInner(String name, VariableKind variableKind) { + String prefix = getPrefixByVariableKind(variableKind); + String suffix = getSuffixByVariableKind(variableKind); + boolean doDecapitalize = false; + + if (name.startsWith(prefix) && name.length() > prefix.length()) { + name = name.substring(prefix.length()); + doDecapitalize = true; + } + + if (name.endsWith(suffix) && name.length() > suffix.length()) { + name = name.substring(0, name.length() - suffix.length()); + doDecapitalize = true; + } + + if (name.startsWith(IS_PREFIX) && name.length() > IS_PREFIX.length() && Character.isUpperCase(name.charAt(IS_PREFIX.length()))) { + name = name.substring(IS_PREFIX.length()); + doDecapitalize = true; + } + + if (doDecapitalize) { + name = Introspector.decapitalize(name); + } + + return name; + } + + @Override + public String propertyNameToVariableName(String propertyName, VariableKind variableKind) { + if (variableKind == VariableKind.STATIC_FINAL_FIELD) { + String[] words = NameUtil.nameToWords(propertyName); + StringBuilder buffer = new StringBuilder(); + for (int i = 0; i < words.length; i++) { + String word = words[i]; + if (i > 0) { + buffer.append("_"); + } + buffer.append(StringUtilRt.toUpperCase(word)); + } + return buffer.toString(); + } + + String prefix = getPrefixByVariableKind(variableKind); + String name = propertyName; + if (name.length() > 0 && prefix.length() > 0 && !StringUtil.endsWithChar(prefix, '_')) { + name = Character.toUpperCase(name.charAt(0)) + name.substring(1); + } + name = prefix + name + getSuffixByVariableKind(variableKind); + name = changeIfNotIdentifier(name); + return name; + } + + private String[] getSuggestionsByName(String name, VariableKind variableKind, boolean isArray, boolean correctKeywords) { + boolean upperCaseStyle = variableKind == VariableKind.STATIC_FINAL_FIELD; + boolean preferLongerNames = getSettings().PREFER_LONGER_NAMES; + String prefix = getPrefixByVariableKind(variableKind); + String suffix = getSuffixByVariableKind(variableKind); + + List answer = new ArrayList(); + for (String suggestion : NameUtil.getSuggestionsByName(name, prefix, suffix, upperCaseStyle, preferLongerNames, isArray)) { + answer.add(correctKeywords ? changeIfNotIdentifier(suggestion) : suggestion); + } + + return ArrayUtil.toStringArray(answer); + } + + @Override + public String suggestUniqueVariableName(String baseName, PsiElement place, boolean lookForward) { + int index = 0; + PsiElement scope = PsiTreeUtil.getNonStrictParentOfType(place, PsiStatement.class, PsiCodeBlock.class, PsiMethod.class); + NextName: + while (true) { + String name = baseName; + if (index > 0) { + name += index; + } + index++; + if (PsiUtil.isVariableNameUnique(name, place)) { + if (lookForward) { + final String name1 = name; + PsiElement run = scope; + while (run != null) { + class CancelException extends RuntimeException { + } + try { + run.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitAnonymousClass(final PsiAnonymousClass aClass) { + } + + @Override public void visitVariable(PsiVariable variable) { + if (name1.equals(variable.getName())) { + throw new CancelException(); + } + } + }); + } + catch (CancelException e) { + continue NextName; + } + run = run.getNextSibling(); + if (scope instanceof PsiMethod) {//do not check next member for param name conflict + break; + } + } + + } + return name; + } + } + } + + @Override + @NotNull + public SuggestedNameInfo suggestUniqueVariableName(@NotNull final SuggestedNameInfo baseNameInfo, + PsiElement place, + boolean ignorePlaceName, + boolean lookForward) { + final String[] names = baseNameInfo.names; + final LinkedHashSet uniqueNames = new LinkedHashSet(names.length); + for (String name : names) { + if (ignorePlaceName && place instanceof PsiNamedElement) { + final String placeName = ((PsiNamedElement)place).getName(); + if (Comparing.strEqual(placeName, name)) { + uniqueNames.add(name); + continue; + } + } + uniqueNames.add(suggestUniqueVariableName(name, place, lookForward)); + } + + return new SuggestedNameInfo(ArrayUtil.toStringArray(uniqueNames)) { + @Override + public void nameChoosen(String name) { + baseNameInfo.nameChoosen(name); + } + }; + } + + private static void sortVariableNameSuggestions(String[] names, + final VariableKind variableKind, + @Nullable final String propertyName, + @Nullable final PsiType type) { + if( names.length <= 1 ) { + return; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("sorting names:" + variableKind); + if (propertyName != null) { + LOG.debug("propertyName:" + propertyName); + } + if (type != null) { + LOG.debug("type:" + type); + } + for (String name : names) { + int count = JavaStatisticsManager.getVariableNameUseCount(name, variableKind, propertyName, type); + LOG.debug(name + " : " + count); + } + } + + Comparator comparator = new Comparator() { + @Override + public int compare(String s1, String s2) { + int count1 = JavaStatisticsManager.getVariableNameUseCount(s1, variableKind, propertyName, type); + int count2 = JavaStatisticsManager.getVariableNameUseCount(s2, variableKind, propertyName, type); + return count2 - count1; + } + }; + Arrays.sort(names, comparator); + } + + @Override + @NotNull + public String getPrefixByVariableKind(VariableKind variableKind) { + String prefix = ""; + switch (variableKind) { + case FIELD: + prefix = getSettings().FIELD_NAME_PREFIX; + break; + case STATIC_FIELD: + prefix = getSettings().STATIC_FIELD_NAME_PREFIX; + break; + case PARAMETER: + prefix = getSettings().PARAMETER_NAME_PREFIX; + break; + case LOCAL_VARIABLE: + prefix = getSettings().LOCAL_VARIABLE_NAME_PREFIX; + break; + case STATIC_FINAL_FIELD: + prefix = ""; + break; + default: + LOG.assertTrue(false); + break; + } + if (prefix == null) { + prefix = ""; + } + return prefix; + } + + @Override + @NotNull + public String getSuffixByVariableKind(VariableKind variableKind) { + String suffix = ""; + switch (variableKind) { + case FIELD: + suffix = getSettings().FIELD_NAME_SUFFIX; + break; + case STATIC_FIELD: + suffix = getSettings().STATIC_FIELD_NAME_SUFFIX; + break; + case PARAMETER: + suffix = getSettings().PARAMETER_NAME_SUFFIX; + break; + case LOCAL_VARIABLE: + suffix = getSettings().LOCAL_VARIABLE_NAME_SUFFIX; + break; + case STATIC_FINAL_FIELD: + suffix = ""; + break; + default: + LOG.assertTrue(false); + break; + } + if (suffix == null) { + suffix = ""; + } + return suffix; + } + + @Nullable + private CodeStyleSettings.TypeToNameMap getMapByVariableKind(VariableKind variableKind) { + if (variableKind == VariableKind.FIELD) return getSettings().FIELD_TYPE_TO_NAME; + if (variableKind == VariableKind.STATIC_FIELD) return getSettings().STATIC_FIELD_TYPE_TO_NAME; + if (variableKind == VariableKind.PARAMETER) return getSettings().PARAMETER_TYPE_TO_NAME; + if (variableKind == VariableKind.LOCAL_VARIABLE) return getSettings().LOCAL_VARIABLE_TYPE_TO_NAME; + return null; + } + + @NonNls + private String changeIfNotIdentifier(String name) { + if (!isIdentifier(name)) { + return StringUtil.fixVariableNameDerivedFromPropertyName(name); + } + return name; + } + + private boolean isIdentifier(String name) { + return JavaPsiFacade.getInstance(myProject).getNameHelper().isIdentifier(name, LanguageLevel.HIGHEST); + } + + private CodeStyleSettings getSettings() { + return CodeStyleSettingsManager.getSettings(myProject); + } + + public static boolean isStringPsiLiteral(PsiElement element) { + if (element instanceof PsiLiteralExpression) { + final String text = element.getText(); + return text.length() > 1 && StringUtil.isQuotedString(text); + } + return false; + } +} diff --git a/java/java-psi-api/src/com/intellij/psi/codeStyle/JavaCodeStyleManager.java b/java/java-psi-api/src/com/intellij/psi/codeStyle/JavaCodeStyleManager.java index 5bc23c8e1cf1..9f24c2a5085c 100644 --- a/java/java-psi-api/src/com/intellij/psi/codeStyle/JavaCodeStyleManager.java +++ b/java/java-psi-api/src/com/intellij/psi/codeStyle/JavaCodeStyleManager.java @@ -1,194 +1,213 @@ -/* - * Copyright 2000-2011 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. - */ - -/* - * @author max - */ -package com.intellij.psi.codeStyle; - -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.project.Project; -import com.intellij.psi.*; -import com.intellij.util.IncorrectOperationException; -import org.intellij.lang.annotations.MagicConstant; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; - -public abstract class JavaCodeStyleManager { - public static JavaCodeStyleManager getInstance(Project project) { - return ServiceManager.getService(project, JavaCodeStyleManager.class); - } - - public static final int DO_NOT_ADD_IMPORTS = 0x1000; - public static final int UNCOMPLETE_CODE = 0x2000; - - public abstract boolean addImport(@NotNull PsiJavaFile file, @NotNull PsiClass refClass); - public abstract PsiElement shortenClassReferences(@NotNull PsiElement element, @MagicConstant(flags = {DO_NOT_ADD_IMPORTS, UNCOMPLETE_CODE}) int flags) throws IncorrectOperationException; - - @NotNull public abstract String getPrefixByVariableKind(VariableKind variableKind); - @NotNull public abstract String getSuffixByVariableKind(VariableKind variableKind); - - public abstract int findEntryIndex(@NotNull PsiImportStatementBase statement); - - /** - * Replaces fully-qualified class names in the contents of the specified element with - * non-qualified names and adds import statements as necessary. - * - * @param element the element to shorten references in. - * @return the element in the PSI tree after the shorten references operation corresponding - * to the original element. - * @throws com.intellij.util.IncorrectOperationException if the file to shorten references in is read-only. - */ - public abstract PsiElement shortenClassReferences(@NotNull PsiElement element) throws IncorrectOperationException; - - /** - * Replaces fully-qualified class names in a part of contents of the specified element with - * non-qualified names and adds import statements as necessary. - * - * @param element the element to shorten references in. - * @param startOffset the start offset in the element of the part where class references are - * shortened. - * @param endOffset the end offset in the element of the part where class references are - * shortened. - * @throws IncorrectOperationException if the file to shorten references in is read-only. - */ - public abstract void shortenClassReferences(@NotNull PsiElement element, int startOffset, int endOffset) throws IncorrectOperationException; - - /** - * Optimizes imports in the specified Java or JSP file. - * - * @param file the file to optimize the imports in. - * @throws IncorrectOperationException if the file is read-only. - */ - public abstract void optimizeImports(@NotNull PsiFile file) throws IncorrectOperationException; - - /** - * Calculates the import list that would be substituted in the specified Java or JSP - * file if an Optimize Imports operation was performed on it. - * - * @param file the file to calculate the import list for. - * @return the calculated import list. - */ - public abstract PsiImportList prepareOptimizeImportsResult(@NotNull PsiJavaFile file); - - /** - * Returns the kind of the specified variable (local, parameter, field, static field or static - * final field). - * - * @param variable the variable to get the kind for. - * @return the variable kind. - */ - public abstract VariableKind getVariableKind(@NotNull PsiVariable variable); - - public SuggestedNameInfo suggestVariableName(@NotNull final VariableKind kind, - @Nullable final String propertyName, - @Nullable final PsiExpression expr, - @Nullable PsiType type) { - return suggestVariableName(kind, propertyName, expr, type, true); - } - - - public abstract SuggestedNameInfo suggestVariableName(@NotNull VariableKind kind, - @Nullable String propertyName, - @Nullable PsiExpression expr, - @Nullable PsiType type, - boolean correctKeywords); - /** - * Generates a stripped-down name (with no code style defined prefixes or suffixes, usable as - * a property name) from the specified name of a variable of the specified kind. - * - * @param name the name of the variable. - * @param variableKind the kind of the variable. - * @return the stripped-down name. - */ - public abstract String variableNameToPropertyName(@NonNls String name, VariableKind variableKind); - - /** - * Appends code style defined prefixes and/or suffixes for the specified variable kind - * to the specified variable name. - * - * @param propertyName the base name of the variable. - * @param variableKind the kind of the variable. - * @return the variable name. - */ - public abstract String propertyNameToVariableName(@NonNls String propertyName, VariableKind variableKind); - - /** - * Suggests a unique name for the variable used at the specified location. - * - * @param baseName the base name for the variable. - * @param place the location where the variable will be used. - * @param lookForward if true, the existing variables are searched in both directions; if false - only backward - * @return the generated unique name, - */ - public abstract String suggestUniqueVariableName(@NonNls String baseName, PsiElement place, boolean lookForward); - - /** - * Suggests a unique name for the variable used at the specified location. - * - * @param baseNameInfo the base name info for the variable. - * @param place the location where the variable will be used. - * @param lookForward if true, the existing variables are searched in both directions; if false - only backward - * @return the generated unique name - */ - @NotNull - public SuggestedNameInfo suggestUniqueVariableName(@NotNull SuggestedNameInfo baseNameInfo, - PsiElement place, - boolean lookForward) { - return suggestUniqueVariableName(baseNameInfo, place, false, lookForward); - } - - /** - * Suggests a unique name for the variable used at the specified location. - * - * - * @param baseNameInfo the base name info for the variable. - * @param place the location where the variable will be used. - * @param ignorePlaceName if true and place is PsiNamedElement, place.getName() would be still treated as unique name - * @param lookForward if true, the existing variables are searched in both directions; if false - only backward @return the generated unique name, - * @return the generated unique name - */ - @NotNull public abstract SuggestedNameInfo suggestUniqueVariableName(@NotNull SuggestedNameInfo baseNameInfo, - PsiElement place, - boolean ignorePlaceName, - boolean lookForward); - - /** - * Replaces all references to Java classes in the contents of the specified element, - * except for references to classes in the same package or in implicitly imported packages, - * with full-qualified references. - * - * @param element the element to replace the references in. - * @return the element in the PSI tree after the qualify operation corresponding to the - * original element. - */ - public abstract PsiElement qualifyClassReferences(@NotNull PsiElement element); - - /** - * Removes unused import statements from the specified Java file. - * - * @param file the file to remove the import statements from. - * @throws IncorrectOperationException if the operation fails for some reason (for example, - * the file is read-only). - */ - public abstract void removeRedundantImports(@NotNull PsiJavaFile file) throws IncorrectOperationException; - - @Nullable - public abstract Collection findRedundantImports(PsiJavaFile file); -} +/* + * Copyright 2000-2011 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. + */ + +/* + * @author max + */ +package com.intellij.psi.codeStyle; + +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.util.IncorrectOperationException; +import org.intellij.lang.annotations.MagicConstant; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +public abstract class JavaCodeStyleManager { + public static JavaCodeStyleManager getInstance(Project project) { + return ServiceManager.getService(project, JavaCodeStyleManager.class); + } + + public static final int DO_NOT_ADD_IMPORTS = 0x1000; + public static final int UNCOMPLETE_CODE = 0x2000; + + public abstract boolean addImport(@NotNull PsiJavaFile file, @NotNull PsiClass refClass); + public abstract PsiElement shortenClassReferences(@NotNull PsiElement element, @MagicConstant(flags = {DO_NOT_ADD_IMPORTS, UNCOMPLETE_CODE}) int flags) throws IncorrectOperationException; + + @NotNull public abstract String getPrefixByVariableKind(VariableKind variableKind); + @NotNull public abstract String getSuffixByVariableKind(VariableKind variableKind); + + public abstract int findEntryIndex(@NotNull PsiImportStatementBase statement); + + /** + * Replaces fully-qualified class names in the contents of the specified element with + * non-qualified names and adds import statements as necessary. + * + * @param element the element to shorten references in. + * @return the element in the PSI tree after the shorten references operation corresponding + * to the original element. + * @throws com.intellij.util.IncorrectOperationException if the file to shorten references in is read-only. + */ + public abstract PsiElement shortenClassReferences(@NotNull PsiElement element) throws IncorrectOperationException; + + /** + * Replaces fully-qualified class names in a part of contents of the specified element with + * non-qualified names and adds import statements as necessary. + * + * @param element the element to shorten references in. + * @param startOffset the start offset in the element of the part where class references are + * shortened. + * @param endOffset the end offset in the element of the part where class references are + * shortened. + * @throws IncorrectOperationException if the file to shorten references in is read-only. + */ + public abstract void shortenClassReferences(@NotNull PsiElement element, int startOffset, int endOffset) throws IncorrectOperationException; + + /** + * Optimizes imports in the specified Java or JSP file. + * + * @param file the file to optimize the imports in. + * @throws IncorrectOperationException if the file is read-only. + */ + public abstract void optimizeImports(@NotNull PsiFile file) throws IncorrectOperationException; + + /** + * Calculates the import list that would be substituted in the specified Java or JSP + * file if an Optimize Imports operation was performed on it. + * + * @param file the file to calculate the import list for. + * @return the calculated import list. + */ + public abstract PsiImportList prepareOptimizeImportsResult(@NotNull PsiJavaFile file); + + /** + * Returns the kind of the specified variable (local, parameter, field, static field or static + * final field). + * + * @param variable the variable to get the kind for. + * @return the variable kind. + */ + public VariableKind getVariableKind(@NotNull PsiVariable variable){ + if (variable instanceof PsiField) { + if (variable.hasModifierProperty(PsiModifier.STATIC)) { + if (variable.hasModifierProperty(PsiModifier.FINAL)) { + return VariableKind.STATIC_FINAL_FIELD; + } + return VariableKind.STATIC_FIELD; + } + return VariableKind.FIELD; + } + else { + if (variable instanceof PsiParameter) { + if (((PsiParameter)variable).getDeclarationScope() instanceof PsiForeachStatement) { + return VariableKind.LOCAL_VARIABLE; + } + return VariableKind.PARAMETER; + } + return VariableKind.LOCAL_VARIABLE; + } + } + + public SuggestedNameInfo suggestVariableName(@NotNull final VariableKind kind, + @Nullable final String propertyName, + @Nullable final PsiExpression expr, + @Nullable PsiType type) { + return suggestVariableName(kind, propertyName, expr, type, true); + } + + + public abstract SuggestedNameInfo suggestVariableName(@NotNull VariableKind kind, + @Nullable String propertyName, + @Nullable PsiExpression expr, + @Nullable PsiType type, + boolean correctKeywords); + /** + * Generates a stripped-down name (with no code style defined prefixes or suffixes, usable as + * a property name) from the specified name of a variable of the specified kind. + * + * @param name the name of the variable. + * @param variableKind the kind of the variable. + * @return the stripped-down name. + */ + public abstract String variableNameToPropertyName(@NonNls String name, VariableKind variableKind); + + /** + * Appends code style defined prefixes and/or suffixes for the specified variable kind + * to the specified variable name. + * + * @param propertyName the base name of the variable. + * @param variableKind the kind of the variable. + * @return the variable name. + */ + public abstract String propertyNameToVariableName(@NonNls String propertyName, VariableKind variableKind); + + /** + * Suggests a unique name for the variable used at the specified location. + * + * @param baseName the base name for the variable. + * @param place the location where the variable will be used. + * @param lookForward if true, the existing variables are searched in both directions; if false - only backward + * @return the generated unique name, + */ + public abstract String suggestUniqueVariableName(@NonNls String baseName, PsiElement place, boolean lookForward); + + /** + * Suggests a unique name for the variable used at the specified location. + * + * @param baseNameInfo the base name info for the variable. + * @param place the location where the variable will be used. + * @param lookForward if true, the existing variables are searched in both directions; if false - only backward + * @return the generated unique name + */ + @NotNull + public SuggestedNameInfo suggestUniqueVariableName(@NotNull SuggestedNameInfo baseNameInfo, + PsiElement place, + boolean lookForward) { + return suggestUniqueVariableName(baseNameInfo, place, false, lookForward); + } + + /** + * Suggests a unique name for the variable used at the specified location. + * + * + * @param baseNameInfo the base name info for the variable. + * @param place the location where the variable will be used. + * @param ignorePlaceName if true and place is PsiNamedElement, place.getName() would be still treated as unique name + * @param lookForward if true, the existing variables are searched in both directions; if false - only backward @return the generated unique name, + * @return the generated unique name + */ + @NotNull public abstract SuggestedNameInfo suggestUniqueVariableName(@NotNull SuggestedNameInfo baseNameInfo, + PsiElement place, + boolean ignorePlaceName, + boolean lookForward); + + /** + * Replaces all references to Java classes in the contents of the specified element, + * except for references to classes in the same package or in implicitly imported packages, + * with full-qualified references. + * + * @param element the element to replace the references in. + * @return the element in the PSI tree after the qualify operation corresponding to the + * original element. + */ + public abstract PsiElement qualifyClassReferences(@NotNull PsiElement element); + + /** + * Removes unused import statements from the specified Java file. + * + * @param file the file to remove the import statements from. + * @throws IncorrectOperationException if the operation fails for some reason (for example, + * the file is read-only). + */ + public abstract void removeRedundantImports(@NotNull PsiJavaFile file) throws IncorrectOperationException; + + @Nullable + public abstract Collection findRedundantImports(PsiJavaFile file); +} diff --git a/java/java-psi-impl/src/com/intellij/core/CoreJavaCodeStyleManager.java b/java/java-psi-impl/src/com/intellij/core/CoreJavaCodeStyleManager.java index 7f651a21df0e..4e9f5e68a6f8 100644 --- a/java/java-psi-impl/src/com/intellij/core/CoreJavaCodeStyleManager.java +++ b/java/java-psi-impl/src/com/intellij/core/CoreJavaCodeStyleManager.java @@ -75,18 +75,13 @@ public class CoreJavaCodeStyleManager extends JavaCodeStyleManager { return null; } - @Override - public VariableKind getVariableKind(@NotNull PsiVariable variable) { - return null; - } - @Override public SuggestedNameInfo suggestVariableName(@NotNull VariableKind kind, @Nullable String propertyName, @Nullable PsiExpression expr, @Nullable PsiType type, boolean correctKeywords) { - return null; + return SuggestedNameInfo.NULL_INFO; } @Override @@ -115,7 +110,7 @@ public class CoreJavaCodeStyleManager extends JavaCodeStyleManager { @Override public PsiElement qualifyClassReferences(@NotNull PsiElement element) { - return null; + return element; } @Override From 74574bcb0f869958389878fae41bc9afbdd8944e Mon Sep 17 00:00:00 2001 From: Evgeny Pasynkov Date: Thu, 10 May 2012 10:07:19 +0200 Subject: [PATCH 30/31] Move EmptyProgressIndicator to core --- .../openapi/progress/EmptyProgressIndicator.java | 9 --------- .../intellij/util/graph/KShortestPathsFinderTest.java | 1 - .../com/intellij/util/continuation/GeneralRunner.java | 5 ----- .../org/jetbrains/idea/maven/MavenImportingTestCase.java | 1 - 4 files changed, 16 deletions(-) rename platform/{platform-api => core-api}/src/com/intellij/openapi/progress/EmptyProgressIndicator.java (94%) diff --git a/platform/platform-api/src/com/intellij/openapi/progress/EmptyProgressIndicator.java b/platform/core-api/src/com/intellij/openapi/progress/EmptyProgressIndicator.java similarity index 94% rename from platform/platform-api/src/com/intellij/openapi/progress/EmptyProgressIndicator.java rename to platform/core-api/src/com/intellij/openapi/progress/EmptyProgressIndicator.java index b367ea3c9aa7..1e745be27edb 100644 --- a/platform/platform-api/src/com/intellij/openapi/progress/EmptyProgressIndicator.java +++ b/platform/core-api/src/com/intellij/openapi/progress/EmptyProgressIndicator.java @@ -95,15 +95,6 @@ public class EmptyProgressIndicator implements ProgressIndicator { return false; } - - public void finish(final Task task) { - myFinished = true; - } - - public boolean isFinished(final Task task) { - return myFinished; - } - public void setIndeterminate(boolean indeterminate) { } diff --git a/platform/platform-tests/testSrc/com/intellij/util/graph/KShortestPathsFinderTest.java b/platform/platform-tests/testSrc/com/intellij/util/graph/KShortestPathsFinderTest.java index daf7f42780af..9763626760d3 100644 --- a/platform/platform-tests/testSrc/com/intellij/util/graph/KShortestPathsFinderTest.java +++ b/platform/platform-tests/testSrc/com/intellij/util/graph/KShortestPathsFinderTest.java @@ -18,7 +18,6 @@ package com.intellij.util.graph; import com.intellij.openapi.progress.EmptyProgressIndicator; import com.intellij.openapi.util.text.StringUtil; import com.intellij.testFramework.UsefulTestCase; -import com.intellij.util.graph.impl.KShortestPathsFinder; import java.util.*; diff --git a/platform/vcs-impl/src/com/intellij/util/continuation/GeneralRunner.java b/platform/vcs-impl/src/com/intellij/util/continuation/GeneralRunner.java index 3a3e1e7bbe3b..6cd97ac1e322 100644 --- a/platform/vcs-impl/src/com/intellij/util/continuation/GeneralRunner.java +++ b/platform/vcs-impl/src/com/intellij/util/continuation/GeneralRunner.java @@ -15,11 +15,7 @@ */ package com.intellij.util.continuation; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.progress.EmptyProgressIndicator; import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator; -import com.intellij.openapi.progress.impl.ProgressManagerImpl; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.CalledInAny; import com.intellij.openapi.vcs.CalledInAwt; @@ -28,7 +24,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; /** * @author irengrig diff --git a/plugins/maven/src/test/java/org/jetbrains/idea/maven/MavenImportingTestCase.java b/plugins/maven/src/test/java/org/jetbrains/idea/maven/MavenImportingTestCase.java index 13b1770f6776..793cb5c86874 100644 --- a/plugins/maven/src/test/java/org/jetbrains/idea/maven/MavenImportingTestCase.java +++ b/plugins/maven/src/test/java/org/jetbrains/idea/maven/MavenImportingTestCase.java @@ -34,7 +34,6 @@ import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.TestDialog; import com.intellij.openapi.util.AsyncResult; -import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VfsUtil; From 4081b7eda276b1f4e6e0954ef1e3c7454c0d1d39 Mon Sep 17 00:00:00 2001 From: Evgeny Pasynkov Date: Thu, 10 May 2012 10:09:18 +0200 Subject: [PATCH 31/31] Instantiate ProgressIndicatorProvider.ourInstance in CoreEnvironment --- .../com/intellij/core/CoreEnvironment.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/platform/core-impl/src/com/intellij/core/CoreEnvironment.java b/platform/core-impl/src/com/intellij/core/CoreEnvironment.java index 60949a95f064..3d683f9bd677 100644 --- a/platform/core-impl/src/com/intellij/core/CoreEnvironment.java +++ b/platform/core-impl/src/com/intellij/core/CoreEnvironment.java @@ -29,6 +29,7 @@ import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.extensions.ExtensionsArea; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileTypes.*; +import com.intellij.openapi.progress.*; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.FileIndexFacade; @@ -148,6 +149,26 @@ public class CoreEnvironment { myProject.registerService(PsiDirectoryFactory.class, new PsiDirectoryFactoryImpl(myPsiManager)); myProject.registerService(ProjectScopeBuilder.class, new CoreProjectScopeBuilder(myProject, myFileIndexFacade)); myProject.registerService(DumbService.class, new MockDumbService(myProject)); + + ProgressIndicatorProvider.ourInstance = new ProgressIndicatorProvider() { + @Override + public ProgressIndicator getProgressIndicator() { + return new EmptyProgressIndicator(); + } + + @Override + protected void doCheckCanceled() throws ProcessCanceledException { + } + + @Override + public NonCancelableSection startNonCancelableSection() { + return new NonCancelableSection() { + @Override + public void done() { + } + }; + } + }; } public Project getProject() {