Merge remote-tracking branch 'origin/master'
authorVladimir.Orlov <Vladimir.Orlov@jetbrains.com>
Thu, 13 Aug 2015 14:03:12 +0000 (17:03 +0300)
committerVladimir.Orlov <Vladimir.Orlov@jetbrains.com>
Thu, 13 Aug 2015 14:03:12 +0000 (17:03 +0300)
83 files changed:
java/debugger/impl/src/com/intellij/debugger/impl/ReloadClassesWorker.java
java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/JavaLineBreakpointType.java
java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/JavaLineBreakpointTypeBase.java
java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/LineBreakpoint.java
java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/RunToCursorBreakpoint.java
java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionData.java
java/java-impl/src/com/intellij/codeInspection/inferNullity/InferNullityAnnotationsAction.java
java/java-tests/testSrc/com/intellij/codeInsight/completion/FragmentCompletionTest.groovy
platform/configuration-store-impl/src/ComponentStoreImpl.kt
platform/configuration-store-impl/src/DefaultProjectStoreImpl.kt
platform/configuration-store-impl/src/ProjectWithModulesStoreImpl.kt
platform/configuration-store-impl/src/StateStorageManagerImpl.kt
platform/configuration-store-impl/src/StoreAwareProjectManager.kt [new file with mode: 0644]
platform/configuration-store-impl/testSrc/ApplicationStoreTest.kt
platform/configuration-store-impl/testSrc/ModuleStoreTest.kt
platform/configuration-store-impl/testSrc/ProjectStoreTest.kt
platform/configuration-store-impl/testSrc/StorageManagerTest.kt
platform/configuration-store-impl/testSrc/XmlElementStorageTest.kt
platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java
platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java
platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionProfileManagerImpl.java
platform/lang-impl/src/com/intellij/execution/impl/ExecutionManagerImpl.java
platform/lang-impl/src/com/intellij/psi/impl/PsiDocumentManagerImpl.java
platform/lang-impl/src/com/intellij/psi/stubs/StubIndexImpl.java
platform/lang-impl/src/com/intellij/psi/stubs/StubUpdatingIndex.java
platform/platform-impl/src/com/intellij/application/options/pathMacros/PathMacroConfigurable.java
platform/platform-impl/src/com/intellij/execution/ExecutableValidator.java
platform/platform-impl/src/com/intellij/ide/customize/PluginGroups.java
platform/platform-impl/src/com/intellij/ide/startupWizard/StartupWizardModel.java
platform/platform-impl/src/com/intellij/ide/ui/AppearanceConfigurable.java
platform/platform-impl/src/com/intellij/openapi/components/impl/stores/FileStorage.java
platform/platform-impl/src/com/intellij/openapi/components/impl/stores/IComponentStore.java
platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StateStorageBase.java
platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StateStorageManager.java
platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StorageUtil.java
platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StoreUtil.java
platform/platform-impl/src/com/intellij/openapi/diff/impl/mergeTool/DiffRequestFactoryImpl.java
platform/platform-impl/src/com/intellij/openapi/options/ex/ConfigurableExtensionPointUtil.java
platform/platform-impl/src/com/intellij/openapi/project/ex/ProjectEx.java
platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectImpl.java
platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.java
platform/platform-resources-en/src/messages/IdeBundle.properties
platform/platform-resources/src/componentSets/Platform.xml
platform/projectModel-api/src/com/intellij/openapi/components/StateStorage.java
platform/testFramework/src/com/intellij/mock/MockProjectEx.java
platform/testFramework/testSrc/com/intellij/testFramework/FixtureRule.kt
platform/testFramework/testSrc/com/intellij/testFramework/TemporaryDirectory.kt
plugins/git4idea/src/git4idea/config/GitExecutableValidator.java
plugins/maven/maven2-server-impl/src/org/jetbrains/idea/maven/server/embedder/Maven2ServerIndexFetcher.java
plugins/maven/maven3-server-common/src/org/jetbrains/idea/maven/server/Maven3ServerIndexFetcher.java
plugins/settings-repository/src/IcsManager.kt
plugins/settings-repository/src/autoSync.kt
plugins/settings-repository/src/sync.kt
python/edu/course-creator-python/resources/META-INF/plugin.xml
python/edu/course-creator-python/src/com/jetbrains/edu/coursecreator/PyCCProjectGenerator.java
python/edu/course-creator-python/src/com/jetbrains/edu/coursecreator/PyCCReferenceResolveProvider.java [new file with mode: 0644]
python/edu/interactive-learning-python/interactive-learning-python.iml
python/edu/interactive-learning-python/resources/META-INF/plugin.xml
python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyReferenceResolveProvider.java [new file with mode: 0644]
python/edu/src/com/jetbrains/python/edu/PyEduUtils.java [new file with mode: 0644]
python/educational/course-creator/src/com/jetbrains/edu/coursecreator/CCFileDeletedListener.java
python/educational/course-creator/src/com/jetbrains/edu/coursecreator/CCRenameHandler.java
python/educational/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java
python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCCreateLesson.java
python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCCreateStudyItemActionBase.java [new file with mode: 0644]
python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCCreateTask.java
python/educational/src/com/jetbrains/edu/EduNames.java
python/educational/src/com/jetbrains/edu/EduUtils.java
python/educational/src/com/jetbrains/edu/courseFormat/AnswerPlaceholder.java
python/educational/src/com/jetbrains/edu/courseFormat/Course.java
python/educational/src/com/jetbrains/edu/courseFormat/Lesson.java
python/educational/src/com/jetbrains/edu/courseFormat/StudyItem.java [moved from python/educational/src/com/jetbrains/edu/courseFormat/Named.java with 55% similarity]
python/educational/src/com/jetbrains/edu/courseFormat/StudyOrderable.java [deleted file]
python/educational/src/com/jetbrains/edu/courseFormat/Task.java
python/educational/src/com/jetbrains/edu/courseFormat/TaskFile.java
python/ipnb/src/org/jetbrains/plugins/ipnb/configuration/IpnbConnectionManager.java
python/src/com/jetbrains/python/sdk/PythonSdkType.java
xml/impl/src/com/intellij/psi/impl/source/xml/DefaultXmlTagNameProvider.java
xml/impl/src/com/intellij/xml/actions/EscapeEntitiesAction.java
xml/tests/src/com/intellij/codeInsight/XmlParsingTest.java
xml/tests/src/com/intellij/codeInsight/editorActions/EscapeEntitiesActionTest.java
xml/tests/testData/psi/testWhitespaceBeforeName.txt [new file with mode: 0644]
xml/xml-psi-impl/src/com/intellij/psi/impl/source/parsing/xml/XmlParsing.java

index a7e3fdbadb1d4b87b6a0f81ce238b9295849e89a..7b3f46e27c8ec6e0fef7c85412e6de7f5d6be034 100644 (file)
@@ -121,6 +121,10 @@ class ReloadClassesWorker {
 
       int processedEntriesCount = 0;
       for (final Map.Entry<String, HotSwapFile> entry : modifiedClasses.entrySet()) {
+        // stop if process is finished already
+        if (debugProcess.isDetached() || debugProcess.isDetaching()) {
+          break;
+        }
         if (redefineProcessor.getProcessedClassesCount() == 0 && myProgress.isCancelled()) {
           // once at least one class has been actually reloaded, do not interrupt the whole process
           break;
index 03eadd84385eba014ac2c8e574074f78e88ed94d..f8b636937600665c8d4bdc0e49a2a460e515846b 100644 (file)
@@ -32,8 +32,11 @@ import com.intellij.xdebugger.breakpoints.XBreakpoint;
 import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
 import com.intellij.xdebugger.breakpoints.ui.XBreakpointGroupingRule;
 import com.intellij.xdebugger.impl.XSourcePositionImpl;
+import org.jetbrains.annotations.Nls;
+import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;
 import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties;
 
 import javax.swing.*;
@@ -50,6 +53,10 @@ public class JavaLineBreakpointType extends JavaLineBreakpointTypeBase<JavaLineB
     super("java-line", DebuggerBundle.message("line.breakpoints.tab.title"));
   }
 
+  protected JavaLineBreakpointType(@NonNls @NotNull String id, @Nls @NotNull String title) {
+    super(id, title);
+  }
+
   //@Override
   protected String getHelpID() {
     return HelpID.LINE_BREAKPOINTS;
@@ -129,7 +136,36 @@ public class JavaLineBreakpointType extends JavaLineBreakpointTypeBase<JavaLineB
     return res;
   }
 
-  class JavaBreakpointVariant extends XLineBreakpointVariant {
+  public boolean matchesPosition(@NotNull LineBreakpoint breakpoint, @NotNull SourcePosition position) {
+    JavaBreakpointProperties properties = breakpoint.getProperties();
+    if (properties == null || properties instanceof JavaLineBreakpointProperties) {
+      if (properties != null && ((JavaLineBreakpointProperties)properties).getLambdaOrdinal() == null) return true;
+      PsiElement containingMethod = getContainingMethod(breakpoint);
+      if (containingMethod == null) return false;
+      return DebuggerUtilsEx.inTheMethod(position, containingMethod);
+    }
+    return true;
+  }
+
+  @Nullable
+  public PsiElement getContainingMethod(@NotNull LineBreakpoint breakpoint) {
+    SourcePosition position = breakpoint.getSourcePosition();
+    if (position == null) return null;
+
+    JavaBreakpointProperties properties = breakpoint.getProperties();
+    if (properties instanceof JavaLineBreakpointProperties) {
+      Integer ordinal = ((JavaLineBreakpointProperties)properties).getLambdaOrdinal();
+      if (ordinal > -1) {
+        List<PsiLambdaExpression> lambdas = DebuggerUtilsEx.collectLambdas(position, true);
+        if (ordinal < lambdas.size()) {
+          return lambdas.get(ordinal);
+        }
+      }
+    }
+    return DebuggerUtilsEx.getContainingMethod(position);
+  }
+
+  public class JavaBreakpointVariant extends XLineBreakpointVariant {
     protected final XSourcePosition mySourcePosition;
 
     private JavaBreakpointVariant(XSourcePosition position) {
@@ -158,7 +194,7 @@ public class JavaLineBreakpointType extends JavaLineBreakpointTypeBase<JavaLineB
     }
   }
 
-  private class ExactJavaBreakpointVariant extends JavaBreakpointVariant {
+  public class ExactJavaBreakpointVariant extends JavaBreakpointVariant {
     private final PsiElement myElement;
     private final Integer myLambdaOrdinal;
 
@@ -200,7 +236,7 @@ public class JavaLineBreakpointType extends JavaLineBreakpointTypeBase<JavaLineB
       if (ordinal != null) {
         Breakpoint javaBreakpoint = BreakpointManager.getJavaBreakpoint(breakpoint);
         if (javaBreakpoint instanceof LineBreakpoint) {
-          PsiElement method = ((LineBreakpoint)javaBreakpoint).getContainingMethod();
+          PsiElement method = getContainingMethod((LineBreakpoint)javaBreakpoint);
           if (method != null) {
             return method.getTextRange();
           }
index 1943a0999ec7b2cdb3447cdc999a54e8d5296cfa..22022e253539ff2815399f33e18bfd986de47a5f 100644 (file)
@@ -81,7 +81,7 @@ public abstract class JavaLineBreakpointTypeBase<P extends JavaBreakpointPropert
   }
 
   @Override
-  public final boolean canPutAt(@NotNull final VirtualFile file, final int line, @NotNull Project project) {
+  public boolean canPutAt(@NotNull final VirtualFile file, final int line, @NotNull Project project) {
     final PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
     // JSPX supports jvm debugging, but not in XHTML files
     if (psiFile == null || psiFile.getViewProvider().getFileType() == StdFileTypes.XHTML) {
index 8a8f8d71118f1d54841bd507adaaf05459231cb1..3f453d24d87d7f1e544de8af3d70a4bd609e3373 100644 (file)
@@ -63,7 +63,6 @@ import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;
-import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties;
 import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
 
 import javax.swing.*;
@@ -210,34 +209,18 @@ public class LineBreakpoint<P extends JavaBreakpointProperties> extends Breakpoi
     return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
       @Override
       public Boolean compute() {
-        if (getProperties() instanceof JavaLineBreakpointProperties) {
-          Integer ordinal = ((JavaLineBreakpointProperties)getProperties()).getLambdaOrdinal();
-          if (ordinal == null) return true;
-          PsiElement containingMethod = getContainingMethod();
-          if (containingMethod == null) return false;
-          SourcePosition position = debugProcess.getPositionManager().getSourcePosition(loc);
-          if (position == null) return false;
-          return DebuggerUtilsEx.inTheMethod(position, containingMethod);
-        }
-        return true;
+        SourcePosition position = debugProcess.getPositionManager().getSourcePosition(loc);
+        if (position == null) return false;
+        JavaLineBreakpointType type = getXBreakpointType();
+        if (type == null) return true;
+        return type.matchesPosition(LineBreakpoint.this, position);
       }
     });
   }
 
   @Nullable
-  public PsiElement getContainingMethod() {
-    SourcePosition position = getSourcePosition();
-    if (position == null) return null;
-    if (getProperties() instanceof JavaLineBreakpointProperties) {
-      Integer ordinal = ((JavaLineBreakpointProperties)getProperties()).getLambdaOrdinal();
-      if (ordinal > -1) {
-        List<PsiLambdaExpression> lambdas = DebuggerUtilsEx.collectLambdas(position, true);
-        if (ordinal < lambdas.size()) {
-          return lambdas.get(ordinal);
-        }
-      }
-    }
-    return DebuggerUtilsEx.getContainingMethod(position);
+  protected JavaLineBreakpointType getXBreakpointType() {
+    return (JavaLineBreakpointType)myXBreakpoint.getType();
   }
 
   private boolean isInScopeOf(DebugProcessImpl debugProcess, String className) {
index bb93a44d9e83268e3a78d56cec91e4a8ebddfbff..ebf9bda914b95f8797b864067a512415f092b951 100644 (file)
@@ -17,15 +17,13 @@ package com.intellij.debugger.ui.breakpoints;
 
 import com.intellij.debugger.SourcePosition;
 import com.intellij.debugger.engine.DebugProcessImpl;
-import com.intellij.debugger.impl.DebuggerUtilsEx;
-import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiFile;
 import com.intellij.psi.PsiManager;
+import com.intellij.xdebugger.XDebuggerUtil;
 import com.intellij.xdebugger.XSourcePosition;
-import com.sun.jdi.Location;
-import com.sun.jdi.ReferenceType;
+import com.intellij.xdebugger.breakpoints.XLineBreakpointType;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;
@@ -47,6 +45,7 @@ public class RunToCursorBreakpoint<P extends JavaBreakpointProperties> extends L
     myRestoreBreakpoints = restoreBreakpoints;
   }
 
+  @NotNull
   @Override
   public SourcePosition getSourcePosition() {
     return myCustomPosition;
@@ -125,17 +124,18 @@ public class RunToCursorBreakpoint<P extends JavaBreakpointProperties> extends L
     return false;  // always enabled
   }
 
+  @Nullable
   @Override
-  protected boolean acceptLocation(final DebugProcessImpl debugProcess, ReferenceType classType, final Location loc) {
-    if (!super.acceptLocation(debugProcess, classType, loc)) return false;
-    return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
-      @Override
-      public Boolean compute() {
-        SourcePosition position = debugProcess.getPositionManager().getSourcePosition(loc);
-        if (position == null) return false;
-        return DebuggerUtilsEx.inTheSameMethod(myCustomPosition, position);
+  protected JavaLineBreakpointType getXBreakpointType() {
+    SourcePosition position = getSourcePosition();
+    VirtualFile file = position.getFile().getVirtualFile();
+    int line = position.getLine();
+    for (XLineBreakpointType<?> type : XDebuggerUtil.getInstance().getLineBreakpointTypes()) {
+      if (type instanceof JavaLineBreakpointType && type.canPutAt(file, line, getProject())) {
+        return ((JavaLineBreakpointType)type);
       }
-    });
+    }
+    return null;
   }
 
   @Nullable
index 5da35bb89ebab6f111be3ee3eaca0179fa733c7e..7ab0d96dca8eaf6f106ee713ba02cce6e10e3494 100644 (file)
@@ -595,7 +595,7 @@ public class JavaCompletionData extends JavaAwareCompletionData {
 
   public static boolean isSuitableForClass(PsiElement position) {
     if (psiElement().afterLeaf("@").accepts(position) ||
-        PsiTreeUtil.getNonStrictParentOfType(position, PsiLiteralExpression.class, PsiComment.class) != null) {
+        PsiTreeUtil.getNonStrictParentOfType(position, PsiLiteralExpression.class, PsiComment.class, PsiExpressionCodeFragment.class) != null) {
       return false;
     }
 
index b0bc31ffdd6d683b08bae56bcd15e325e577df89..7633d1d886ef03c20b699be5d7bbcae3efd5e36e 100644 (file)
@@ -33,6 +33,7 @@ import com.intellij.openapi.module.Module;
 import com.intellij.openapi.module.ModuleUtilCore;
 import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.project.DumbService;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.project.ProjectUtil;
 import com.intellij.openapi.roots.DependencyScope;
@@ -271,7 +272,7 @@ public class InferNullityAnnotationsAction extends BaseAnalysisAction {
   }
 
   private void restartAnalysis(final Project project, final AnalysisScope scope) {
-    ApplicationManager.getApplication().invokeLater(new Runnable() {
+    DumbService.getInstance(project).smartInvokeLater(new Runnable() {
       @Override
       public void run() {
         analyze(project, scope);
index 4a5a6d0db3ea333cb72296d719c946188bf9e0f6..a9b22b5ada213467d2169601336879f740176a49 100644 (file)
@@ -129,5 +129,15 @@ public class FragmentCompletionTest extends LightCodeInsightFixtureTestCase {
     assert !myFixture.lookupElementStrings.contains('valueOf')
   }
 
+  public void "test no class keywords in expression fragment"() {
+    def ctxFile = myFixture.addClass("package foo; public class Class {{\n int a = 2; }}").containingFile
+    def context = ctxFile.findElementAt(ctxFile.text.indexOf('int'))
+
+    PsiFile file = JavaCodeFragmentFactory.getInstance(project).createExpressionCodeFragment("", context, null, true);
+    myFixture.configureFromExistingVirtualFile(file.getVirtualFile());
+    myFixture.completeBasic()
+    assert !myFixture.lookupElementStrings.contains('enum')
+    assert !myFixture.lookupElementStrings.contains('class')
+  }
 
 }
index 86ee8f8e249ce214d44944bebe80fdda75325407..076985526ff2ec042f35be00b2751b0955e2208b 100644 (file)
@@ -67,7 +67,7 @@ public abstract class ComponentStoreImpl : IComponentStore {
   // return null if not applicable
   protected open fun selectDefaultStorages(storages: Array<Storage>, operation: StateStorageOperation): Array<Storage>? = null
 
-  override fun initComponent(component: Any, service: Boolean) {
+  override final fun initComponent(component: Any, service: Boolean) {
     if (component is SettingsSavingComponent) {
       mySettingsSavingComponents.add(component)
     }
@@ -79,7 +79,9 @@ public abstract class ComponentStoreImpl : IComponentStore {
     val componentNameIfStateExists: String?
     try {
       componentNameIfStateExists = if (component is PersistentStateComponent<*>) {
-        initPersistentComponent(component, null, false)
+        val stateSpec = StoreUtil.getStateSpec(component)
+        doAddComponent(stateSpec.name, component)
+        initPersistentComponent(stateSpec, component, null, false)
       }
       else {
         initJdomExternalizable(component as JDOMExternalizable)
@@ -98,10 +100,7 @@ public abstract class ComponentStoreImpl : IComponentStore {
       val project = this.project
       val app = ApplicationManager.getApplication()
       if (project != null && !app.isHeadlessEnvironment() && !app.isUnitTestMode() && project.isInitialized()) {
-        val substitutor = storageManager.getMacroSubstitutor()
-        if (substitutor != null) {
-          StorageUtil.notifyUnknownMacros(substitutor, project, componentNameIfStateExists)
-        }
+        StorageUtil.notifyUnknownMacros(this, project, componentNameIfStateExists)
       }
     }
   }
@@ -207,7 +206,7 @@ public abstract class ComponentStoreImpl : IComponentStore {
       LOG.error(e)
     }
 
-    val element = storageManager.getOldStorage(component, componentName, StateStorageOperation.READ)?.getState(component, componentName, javaClass<Element>(), null) ?: return null
+    val element = storageManager.getOldStorage(component, componentName, StateStorageOperation.READ)?.getState(component, componentName, javaClass<Element>()) ?: return null
     try {
       component.readExternal(element)
     }
@@ -226,16 +225,12 @@ public abstract class ComponentStoreImpl : IComponentStore {
     }
   }
 
-  private fun <T> initPersistentComponent(component: PersistentStateComponent<T>, changedStorages: Set<StateStorage>?, reloadData: Boolean): String? {
-    val stateSpec = StoreUtil.getStateSpec(component)
-    val name = stateSpec.name
-    if (changedStorages == null || !reloadData) {
-      doAddComponent(name, component)
-    }
+  private fun <T> initPersistentComponent(stateSpec: State, component: PersistentStateComponent<T>, changedStorages: Set<StateStorage>?, reloadData: Boolean): String? {
     if (optimizeTestLoading()) {
       return null
     }
 
+    val name = stateSpec.name
     val stateClass = ComponentSerializationUtil.getStateClass<T>(component.javaClass)
     if (!stateSpec.defaultStateAsResource && LOG.isDebugEnabled() && getDefaultState(component, name, stateClass) != null) {
       LOG.error("$name has default state, but not marked to load it")
@@ -251,7 +246,7 @@ public abstract class ComponentStoreImpl : IComponentStore {
       }
 
       val storage = storageManager.getStateStorage(storageSpec)
-      var state = storage.getState(component, name, stateClass, defaultState)
+      var state = storage.getState(component, name, stateClass, defaultState, reloadData)
       if (state == null) {
         if (changedStorages != null && changedStorages.contains(storage)) {
           // state will be null if file deleted
@@ -326,7 +321,7 @@ public abstract class ComponentStoreImpl : IComponentStore {
 
   protected open fun optimizeTestLoading(): Boolean = false
 
-  override fun isReloadPossible(componentNames: MutableSet<String>): Boolean {
+  override final fun isReloadPossible(componentNames: MutableSet<String>): Boolean {
     for (componentName in componentNames) {
       val component = myComponents.get(componentName)
       if (component != null && (component !is PersistentStateComponent<*> || !StoreUtil.getStateSpec(component).reloadable)) {
@@ -337,7 +332,7 @@ public abstract class ComponentStoreImpl : IComponentStore {
     return true
   }
 
-  override fun getNotReloadableComponents(componentNames: MutableCollection<String>): Collection<String> {
+  override final fun getNotReloadableComponents(componentNames: MutableCollection<String>): Collection<String> {
     var notReloadableComponents: MutableSet<String>? = null
     for (componentName in componentNames) {
       val component = myComponents.get(componentName)
@@ -351,25 +346,38 @@ public abstract class ComponentStoreImpl : IComponentStore {
     return notReloadableComponents ?: emptySet<String>()
   }
 
-  override fun reinitComponents(componentNames: MutableSet<String>, reloadData: Boolean) {
-    reinitComponents(componentNames, emptySet<String>(), Collections.emptySet<StateStorage>())
+  override final fun reloadStates(componentNames: MutableSet<String>) {
+    reinitComponents(componentNames)
+  }
+
+  override final fun reloadState(componentClass: Class<out PersistentStateComponent<*>>) {
+    val stateSpec = StoreUtil.getStateSpecOrError(componentClass)
+    val component = myComponents.get(stateSpec.name) as PersistentStateComponent<*>?
+    if (component != null) {
+      initPersistentComponent(stateSpec, component, emptySet(), true)
+    }
   }
 
-  override fun reinitComponent(componentName: String, changedStorages: MutableSet<StateStorage>): Boolean {
+  private fun reloadState(componentName: String, changedStorages: Set<StateStorage>): Boolean {
     val component = myComponents.get(componentName) as PersistentStateComponent<*>?
     if (component == null) {
       return false
     }
     else {
       val changedStoragesEmpty = changedStorages.isEmpty()
-      initPersistentComponent(component, if (changedStoragesEmpty) null else changedStorages, changedStoragesEmpty)
+      initPersistentComponent(StoreUtil.getStateSpec(component), component, if (changedStoragesEmpty) null else changedStorages, changedStoragesEmpty)
       return true
     }
   }
 
   protected abstract fun getMessageBus(): MessageBus
 
-  override fun reload(changedStorages: MutableSet<StateStorage>): Collection<String>? {
+  /**
+   * null if reloaded
+   * empty list if nothing to reload
+   * list of not reloadable components (reload is not performed)
+   */
+  fun reload(changedStorages: Set<StateStorage>): Collection<String>? {
     if (changedStorages.isEmpty()) {
       return emptySet()
     }
@@ -391,18 +399,18 @@ public abstract class ComponentStoreImpl : IComponentStore {
     }
 
     val notReloadableComponents = getNotReloadableComponents(componentNames)
-    reinitComponents(componentNames, notReloadableComponents, changedStorages)
+    reinitComponents(componentNames, changedStorages, notReloadableComponents)
     return if (notReloadableComponents.isEmpty()) null else notReloadableComponents
   }
 
   // used in settings repository plugin
-  public fun reinitComponents(componentNames: Set<String>, notReloadableComponents: Collection<String>, changedStorages: MutableSet<StateStorage>) {
+  public fun reinitComponents(componentNames: Set<String>, changedStorages: Set<StateStorage> = emptySet(), notReloadableComponents: Collection<String> = emptySet()) {
     val messageBus = getMessageBus()
     messageBus.syncPublisher(BatchUpdateListener.TOPIC).onBatchUpdateStarted()
     try {
       for (componentName in componentNames) {
         if (!notReloadableComponents.contains(componentName)) {
-          reinitComponent(componentName, changedStorages)
+          reloadState(componentName, changedStorages)
         }
       }
     }
index 161dabd048e6369d8424c96dfdf702731b36306e..9a61cf76c7da75ae1f8e7306466c964ac8713751 100644 (file)
@@ -17,12 +17,10 @@ package com.intellij.configurationStore
 
 import com.intellij.openapi.application.ApplicationManager
 import com.intellij.openapi.components.*
-import com.intellij.openapi.components.impl.stores.FileStorage
 import com.intellij.openapi.components.impl.stores.StateStorageManager
 import com.intellij.openapi.components.impl.stores.StreamProvider
 import com.intellij.openapi.project.Project
 import com.intellij.openapi.project.impl.ProjectImpl
-import com.intellij.openapi.util.Couple
 import com.intellij.util.containers.ContainerUtil
 import org.jdom.Element
 import java.io.File
@@ -68,8 +66,6 @@ class DefaultProjectStoreImpl(override val project: ProjectImpl, private val pat
 
     override fun getStateStorage(fileSpec: String, roamingType: RoamingType) = storage
 
-    override fun getCachedFileStateStorages(changed: Collection<String>, deleted: Collection<String>): Couple<Collection<FileStorage>> = Couple(emptyList<FileStorage>(), emptyList<FileStorage>())
-
     override fun startExternalization(): StateStorageManager.ExternalizationSession? {
       val externalizationSession = storage.startExternalization()
       return if (externalizationSession == null) null else MyExternalizationSession(externalizationSession)
index c02e131a2e738cf6f80e63543eb3b1945360efc5..de47c5a3fb3d5cbbd9d572b9c913a40770536f1a 100644 (file)
@@ -16,7 +16,6 @@
 package com.intellij.configurationStore
 
 import com.intellij.openapi.components.PathMacroManager
-import com.intellij.openapi.components.StateStorage
 import com.intellij.openapi.components.StateStorage.SaveSession
 import com.intellij.openapi.components.TrackingPathMacroSubstitutor
 import com.intellij.openapi.components.impl.stores.IComponentStore
@@ -37,18 +36,6 @@ class PlatformLangProjectStoreClassProvider : ProjectStoreClassProvider {
 }
 
 class ProjectWithModulesStoreImpl(project: ProjectImpl, pathMacroManager: PathMacroManager) : ProjectStoreImpl(project, pathMacroManager) {
-  override fun reinitComponent(componentName: String, changedStorages: Set<StateStorage>): Boolean {
-    if (super.reinitComponent(componentName, changedStorages)) {
-      return true
-    }
-
-    for (module in getPersistentModules()) {
-      // we have to reinit all modules for component because we don't know affected module
-      module.stateStore.reinitComponent(componentName, changedStorages)
-    }
-    return true
-  }
-
   override fun getSubstitutors(): List<TrackingPathMacroSubstitutor> {
     val result = SmartList<TrackingPathMacroSubstitutor>()
     ContainerUtil.addIfNotNull(result, storageManager.getMacroSubstitutor())
@@ -59,20 +46,6 @@ class ProjectWithModulesStoreImpl(project: ProjectImpl, pathMacroManager: PathMa
     return result
   }
 
-  override fun isReloadPossible(componentNames: Set<String>): Boolean {
-    if (!super.isReloadPossible(componentNames)) {
-      return false
-    }
-
-    for (module in getPersistentModules()) {
-      if (!module.stateStore.isReloadPossible(componentNames)) {
-        return false
-      }
-    }
-
-    return true
-  }
-
   private fun getPersistentModules() = ModuleManager.getInstance(project)?.getModules() ?: Module.EMPTY_ARRAY
 
   override protected fun beforeSave(readonlyFiles: List<Pair<SaveSession, VirtualFile>>) {
index 4c5fdc11ff910c9ec342b01974e53237930993ef..b473d82ec207e2cd067fdf65c574a2298cc11531 100644 (file)
@@ -22,10 +22,8 @@ import com.intellij.openapi.components.*
 import com.intellij.openapi.components.StateStorage.SaveSession
 import com.intellij.openapi.components.StateStorageChooserEx.Resolution
 import com.intellij.openapi.components.impl.stores.DirectoryBasedStorage
-import com.intellij.openapi.components.impl.stores.FileStorage
 import com.intellij.openapi.components.impl.stores.StateStorageManager
 import com.intellij.openapi.components.impl.stores.StreamProvider
-import com.intellij.openapi.util.Couple
 import com.intellij.openapi.util.Disposer
 import com.intellij.openapi.util.io.FileUtilRt
 import com.intellij.openapi.util.text.StringUtil
@@ -156,28 +154,27 @@ open class StateStorageManagerImpl(private val rootTagName: String,
     }
   }
 
-  override final fun getCachedFileStateStorages(changed: MutableCollection<String>,
-                                                deleted: MutableCollection<String>): Couple<MutableCollection<FileStorage>> {
-    val result = storageLock.withLock { Couple.of(getCachedFileStorages(changed), getCachedFileStorages(deleted)) }
-    return Couple.of(result.first.toMutableSet(), result.second.toMutableSet())
-  }
 
-  fun getCachedFileStorages(fileSpecs: Collection<String>): Collection<FileStorage> {
+  fun getCachedFileStorages(changed: Collection<String>, deleted: Collection<String>) = storageLock.withLock { Pair(getCachedFileStorages(changed), getCachedFileStorages(deleted)) }
+
+  fun getCachedFileStorages(fileSpecs: Collection<String>): Collection<StateStorage> {
     if (fileSpecs.isEmpty()) {
       return emptyList()
     }
 
-    var result: MutableList<FileBasedStorage>? = null
-    for (fileSpec in fileSpecs) {
-      val storage = storages.get(fileSpec)
-      if (storage is FileBasedStorage) {
-        if (result == null) {
-          result = SmartList<FileBasedStorage>()
+    storageLock.withLock {
+      var result: MutableList<FileBasedStorage>? = null
+      for (fileSpec in fileSpecs) {
+        val storage = storages.get(fileSpec)
+        if (storage is FileBasedStorage) {
+          if (result == null) {
+            result = SmartList<FileBasedStorage>()
+          }
+          result.add(storage)
         }
-        result.add(storage)
       }
+      return result ?: emptyList<FileBasedStorage>()
     }
-    return result ?: emptyList<FileBasedStorage>()
   }
 
   // overridden in upsource
diff --git a/platform/configuration-store-impl/src/StoreAwareProjectManager.kt b/platform/configuration-store-impl/src/StoreAwareProjectManager.kt
new file mode 100644 (file)
index 0000000..b1290dd
--- /dev/null
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2000-2015 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.configurationStore
+
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.application.ApplicationNamesInfo
+import com.intellij.openapi.application.ModalityState
+import com.intellij.openapi.application.WriteAction
+import com.intellij.openapi.application.ex.ApplicationManagerEx
+import com.intellij.openapi.components.ComponentManager
+import com.intellij.openapi.components.StateStorage
+import com.intellij.openapi.components.impl.stores.*
+import com.intellij.openapi.components.stateStore
+import com.intellij.openapi.module.Module
+import com.intellij.openapi.progress.ProgressManager
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.project.ProjectBundle
+import com.intellij.openapi.project.impl.ProjectManagerImpl
+import com.intellij.openapi.ui.Messages
+import com.intellij.openapi.util.Key
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.openapi.vfs.VirtualFileManager
+import com.intellij.openapi.vfs.ex.VirtualFileManagerAdapter
+import com.intellij.openapi.vfs.newvfs.events.VFileEvent
+import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent
+import com.intellij.util.SingleAlarm
+import com.intellij.util.containers.MultiMap
+import gnu.trove.THashSet
+import org.jetbrains.annotations.TestOnly
+import java.util.LinkedHashSet
+import java.util.concurrent.atomic.AtomicInteger
+
+/**
+ * Should be a separate service, not closely related to ProjectManager, but it requires some cleanup/investigation.
+ */
+class StoreAwareProjectManager(virtualFileManager: VirtualFileManager, progressManager: ProgressManager) : ProjectManagerImpl(progressManager) {
+  companion object {
+    private val CHANGED_FILES_KEY = Key.create<MultiMap<ComponentStoreImpl, StateStorage>>("CHANGED_FILES_KEY")
+  }
+
+  private val reloadBlockCount = AtomicInteger()
+  private val changedApplicationFiles = LinkedHashSet<StateStorage>()
+
+  private val restartApplicationOrReloadProjectTask = Runnable {
+    if (isReloadUnblocked() && tryToReloadApplication()) {
+      val projectsToReload = THashSet<Project>()
+      for (project in getOpenProjects()) {
+        if (project.isDisposed()) {
+          continue
+        }
+
+        val changes = CHANGED_FILES_KEY.get(project) ?: continue
+        CHANGED_FILES_KEY.set(project, null)
+        if (!changes.isEmpty()) {
+          for ((store, storages) in changes.entrySet()) {
+            @suppress("UNCHECKED_CAST")
+            if (reloadStore(storages as Set<StateStorage>, store, false) == ReloadComponentStoreStatus.RESTART_AGREED) {
+              projectsToReload.add(project)
+            }
+          }
+        }
+      }
+
+      for (project in projectsToReload) {
+        ProjectManagerImpl.doReloadProject(project)
+      }
+    }
+  }
+
+  private val changedFilesAlarm = SingleAlarm(restartApplicationOrReloadProjectTask, 300, this)
+
+  init {
+    ApplicationManager.getApplication().getMessageBus().connect().subscribe(StateStorageManager.STORAGE_TOPIC, object : StorageManagerListener() {
+      override fun storageFileChanged(event: VFileEvent, storage: StateStorage, componentManager: ComponentManager) {
+        if (event is VFilePropertyChangeEvent) {
+          // ignore because doesn't affect content
+          return
+        }
+
+        if (event.getRequestor() is StateStorage.SaveSession || event.getRequestor() is StateStorage || event.getRequestor() is ProjectManagerImpl) {
+          return
+        }
+
+        registerChangedStorage(storage, componentManager)
+      }
+    })
+
+    virtualFileManager.addVirtualFileManagerListener(object : VirtualFileManagerAdapter() {
+      override fun beforeRefreshStart(asynchronous: Boolean) {
+        blockReloadingProjectOnExternalChanges()
+      }
+
+      override fun afterRefreshFinish(asynchronous: Boolean) {
+        unblockReloadingProjectOnExternalChanges()
+      }
+    })
+  }
+
+  private fun isReloadUnblocked(): Boolean {
+    val count = reloadBlockCount.get()
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("[RELOAD] myReloadBlockCount = $count")
+    }
+    return count == 0
+  }
+
+  override fun saveChangedProjectFile(file: VirtualFile, project: Project) {
+    val storageManager = (project.stateStore as ComponentStoreImpl).storageManager as? StateStorageManagerImpl ?: return
+    storageManager.getCachedFileStorages(listOf(storageManager.collapseMacros(file.getPath()))).firstOrNull()?.let {
+      // if empty, so, storage is not yet loaded, so, we don't have to reload
+      registerChangedStorage(it, project)
+    }
+  }
+
+  override fun blockReloadingProjectOnExternalChanges() {
+    reloadBlockCount.incrementAndGet()
+  }
+
+  override fun unblockReloadingProjectOnExternalChanges() {
+    assert(reloadBlockCount.get() > 0)
+    if (reloadBlockCount.decrementAndGet() == 0 && changedFilesAlarm.isEmpty()) {
+      if (ApplicationManager.getApplication().isUnitTestMode()) {
+        // todo fix test to handle invokeLater
+        changedFilesAlarm.request(true)
+      }
+      else {
+        ApplicationManager.getApplication().invokeLater(restartApplicationOrReloadProjectTask, ModalityState.NON_MODAL)
+      }
+    }
+  }
+
+  @TestOnly fun flushChangedAlarm() {
+    changedFilesAlarm.flush()
+  }
+
+  override fun reloadProject(project: Project) {
+    CHANGED_FILES_KEY.set(project, null)
+    super.reloadProject(project)
+  }
+
+  private fun registerChangedStorage(storage: StateStorage, componentManager: ComponentManager) {
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("[RELOAD] Registering project to reload: $storage", Exception())
+    }
+
+    val project: Project? = when (componentManager) {
+      is Project -> componentManager
+      is Module -> componentManager.getProject()
+      else -> null
+    }
+
+    if (project == null) {
+      val changes = changedApplicationFiles
+      synchronized (changes) {
+        changes.add(storage)
+      }
+    }
+    else {
+      var changes = CHANGED_FILES_KEY.get(project)
+      if (changes == null) {
+        changes = MultiMap.createLinkedSet()
+        CHANGED_FILES_KEY.set(project, changes)
+      }
+
+      synchronized (changes) {
+        changes.putValue(componentManager.stateStore as ComponentStoreImpl, storage)
+      }
+    }
+
+    if (storage is StateStorageBase<*>) {
+      storage.disableSaving()
+    }
+
+    if (isReloadUnblocked()) {
+      changedFilesAlarm.cancelAndRequest()
+    }
+  }
+
+  private fun tryToReloadApplication(): Boolean {
+    if (ApplicationManager.getApplication().isDisposed()) {
+      return false
+    }
+
+    if (changedApplicationFiles.isEmpty()) {
+      return true
+    }
+
+    val changes = LinkedHashSet<StateStorage>(changedApplicationFiles)
+    changedApplicationFiles.clear()
+
+    val status = reloadStore(changes, ApplicationManager.getApplication().stateStore as ComponentStoreImpl, true)
+    if (status == ReloadComponentStoreStatus.RESTART_AGREED) {
+      ApplicationManagerEx.getApplicationEx().restart(true)
+      return false
+    }
+    else {
+      return status == ReloadComponentStoreStatus.SUCCESS || status == ReloadComponentStoreStatus.RESTART_CANCELLED
+    }
+  }
+}
+
+private fun reloadStore(changedStorages: Set<StateStorage>, store: ComponentStoreImpl, isApp: Boolean): ReloadComponentStoreStatus {
+  val notReloadableComponents: Collection<String>?
+  var willBeReloaded = false
+  try {
+    val token = WriteAction.start()
+    try {
+      notReloadableComponents = store.reload(changedStorages)
+    }
+    catch (e: Throwable) {
+      LOG.warn(e)
+      Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()), ProjectBundle.message("project.reload.failed.title"))
+      return ReloadComponentStoreStatus.ERROR
+    }
+    finally {
+      token.finish()
+    }
+
+    if (notReloadableComponents == null || notReloadableComponents.isEmpty()) {
+      return ReloadComponentStoreStatus.SUCCESS
+    }
+
+    willBeReloaded = askToRestart(store, notReloadableComponents, changedStorages, isApp)
+    return if (willBeReloaded) ReloadComponentStoreStatus.RESTART_AGREED else ReloadComponentStoreStatus.RESTART_CANCELLED
+  }
+  finally {
+    if (!willBeReloaded) {
+      for (storage in changedStorages) {
+        if (storage is StateStorageBase<*>) {
+          storage.enableSaving()
+        }
+      }
+    }
+  }
+}
+
+// used in settings repository plugin
+fun askToRestart(store: IComponentStore, notReloadableComponents: Collection<String>, changedStorages: Set<StateStorage>?, isApp: Boolean): Boolean {
+  val message = StringBuilder()
+  val storeName = if (store is IProjectStore) "Project" else "Application"
+  message.append(storeName).append(' ')
+  message.append("components were changed externally and cannot be reloaded:\n\n")
+  var count = 0
+  for (component in notReloadableComponents) {
+    if (count == 10) {
+      message.append('\n').append("and ").append(notReloadableComponents.size() - count).append(" more").append('\n')
+    }
+    else {
+      message.append(component).append('\n')
+      count++
+    }
+  }
+
+  message.append("\nWould you like to ")
+  if (isApp) {
+    message.append(if (ApplicationManager.getApplication().isRestartCapable()) "restart" else "shutdown").append(' ')
+    message.append(ApplicationNamesInfo.getInstance().getProductName()).append('?')
+  }
+  else {
+    message.append("reload project?")
+  }
+
+  if (Messages.showYesNoDialog(message.toString(), "$storeName Files Changed", Messages.getQuestionIcon()) == Messages.YES) {
+    if (changedStorages != null) {
+      for (storage in changedStorages) {
+        if (storage is StateStorageBase<*>) {
+          storage.disableSaving()
+        }
+      }
+    }
+    return true
+  }
+  return false
+}
+
+public enum class ReloadComponentStoreStatus {
+  RESTART_AGREED,
+  RESTART_CANCELLED,
+  ERROR,
+  SUCCESS
+}
\ No newline at end of file
index ebe5f2eb5dd268ed9bd73b754fbc1642543ffcca..0c0401dd2e253d30d48c47d36adb8fbae563b91c 100644 (file)
@@ -19,15 +19,12 @@ import com.intellij.application.options.PathMacrosImpl
 import com.intellij.openapi.application.ApplicationManager
 import com.intellij.openapi.application.runWriteAction
 import com.intellij.openapi.components.*
-import com.intellij.openapi.components.impl.stores.StoreUtil
 import com.intellij.openapi.components.impl.stores.StreamProvider
 import com.intellij.openapi.util.io.FileUtilRt
 import com.intellij.openapi.vfs.CharsetToolkit
 import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.testFramework.ProjectRule
-import com.intellij.testFramework.TemporaryDirectory
-import com.intellij.testFramework.VfsTestUtil
-import com.intellij.testFramework.runInEdtAndWait
+import com.intellij.testFramework.*
+import com.intellij.util.SmartList
 import com.intellij.util.xmlb.XmlSerializerUtil
 import gnu.trove.THashMap
 import org.hamcrest.CoreMatchers.equalTo
@@ -49,6 +46,9 @@ class ApplicationStoreTest {
   private val tempDirManager = TemporaryDirectory()
   public Rule fun getTemporaryFolder(): TemporaryDirectory = tempDirManager
 
+  private val edtRule = EdtRule()
+  public Rule fun _edtRule(): EdtRule = edtRule
+
   private var testAppConfig: VirtualFile by Delegates.notNull()
   private var componentStore: MyComponentStore by Delegates.notNull()
 
@@ -65,7 +65,7 @@ class ApplicationStoreTest {
 
     componentStore.initComponent(component, false)
     component.foo = "newValue"
-    StoreUtil.save(componentStore, null)
+    componentStore.save(SmartList())
 
     assertThat<String>(streamProvider.data.get(RoamingType.PER_USER)!!.get(StoragePathMacros.APP_CONFIG + "/proxy.settings.xml"), equalTo("<application>\n" + "  <component name=\"HttpConfigurable\">\n" + "    <option name=\"foo\" value=\"newValue\" />\n" + "  </component>\n" + "</application>"))
   }
@@ -83,32 +83,28 @@ class ApplicationStoreTest {
     assertThat(component.foo, equalTo("newValue"))
   }
 
-  public Test fun `remove deprecated storage on write`() {
+  public @Test @RunsInEdt fun `remove deprecated storage on write`() {
     doRemoveDeprecatedStorageOnWrite(SeveralStoragesConfigured())
   }
 
-  public Test fun `remove deprecated storage on write 2`() {
+  public @Test @RunsInEdt fun `remove deprecated storage on write 2`() {
     doRemoveDeprecatedStorageOnWrite(ActualStorageLast())
   }
 
   private fun doRemoveDeprecatedStorageOnWrite(component: Foo) {
-    val oldFile = saveConfig("other.xml", "<application><component name=\"HttpConfigurable\"><option name=\"foo\" value=\"old\" /></component></application>")
-    saveConfig("proxy.settings.xml", "<application><component name=\"HttpConfigurable\"><option name=\"foo\" value=\"new\" /></component></application>")
+    val oldFile = writeConfig("other.xml", "<application><component name=\"HttpConfigurable\"><option name=\"foo\" value=\"old\" /></component></application>")
+    writeConfig("proxy.settings.xml", "<application><component name=\"HttpConfigurable\"><option name=\"foo\" value=\"new\" /></component></application>")
 
     componentStore.initComponent(component, false)
     assertThat(component.foo, equalTo("new"))
 
     component.foo = "new2"
-    runInEdtAndWait { StoreUtil.save(componentStore, null) }
+    componentStore.save(SmartList())
 
     assertThat(oldFile.exists(), equalTo(false))
   }
 
-  private fun saveConfig(fileName: String, Language("XML") data: String): VirtualFile {
-    var result: VirtualFile? = null
-    runInEdtAndWait { runWriteAction { result = VfsTestUtil.createFile(testAppConfig, fileName, data) } }
-    return result!!
-  }
+  private fun writeConfig(fileName: String, Language("XML") data: String) = runWriteAction { testAppConfig.writeChild(fileName, data) }
 
   private class MyStreamProvider : StreamProvider {
     public val data: MutableMap<RoamingType, MutableMap<String, String>> = THashMap()
@@ -181,3 +177,8 @@ class ApplicationStoreTest {
     }
   }
 }
+
+fun VirtualFile.writeChild(relativePath: String, data: String) = VfsTestUtil.createFile(this, relativePath, data)
+
+val VirtualFile.path: String
+  get() = getPath()
\ No newline at end of file
index f298d52804200887c0a31f6bde42880b14299f55..5a32d3eac2f83b9f6f613ec188f3e04c5df79b11 100644 (file)
@@ -22,9 +22,9 @@ import org.junit.Rule
 import org.junit.Test
 import java.io.File
 
-class ModuleStoreTest {
+@RunsInEdt class ModuleStoreTest {
   companion object {
-     ClassRule val projectRule: ProjectRule = ProjectRule()
+     ClassRule val projectRule = ProjectRule()
 
     val MODULE_DIR = "\$MODULE_DIR$"
 
@@ -44,49 +44,48 @@ class ModuleStoreTest {
 
   private val tempDirManager = TemporaryDirectory()
 
-  private val ruleChain = RuleChain(tempDirManager)
+  private val ruleChain = RuleChain(tempDirManager, EdtRule())
 
   public Rule fun getChain(): RuleChain = ruleChain
 
-  public Test fun `set option`() {
-    runInEdtAndWait {
-      val moduleFile = runWriteAction { VfsTestUtil.createFile(tempDirManager.newVirtualDirectory("module"), "test.iml", "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
-        "<module type=\"JAVA_MODULE\" foo=\"bar\" version=\"4\" />") }
+  public @Test fun `set option`() {
+    val moduleFile = runWriteAction {
+      VfsTestUtil.createFile(tempDirManager.newVirtualDirectory("module"), "test.iml", "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+        "<module type=\"JAVA_MODULE\" foo=\"bar\" version=\"4\" />")
+    }
+
+    projectRule.project.runInStoreLoadMode {
+      moduleFile.loadModule().useAndDispose {
+        assertThat(getOptionValue("foo"), equalTo("bar"))
 
-      projectRule.project.runInStoreLoadMode {
-        moduleFile.loadModule().useAndDispose {
-          assertThat(getOptionValue("foo"), equalTo("bar"))
+        setOption("foo", "not bar")
+        saveStore()
+      }
 
-          setOption("foo", "not bar")
-          saveStore()
-        }
+      moduleFile.loadModule().useAndDispose {
+        assertThat(getOptionValue("foo"), equalTo("not bar"))
 
-        moduleFile.loadModule().useAndDispose {
-          assertThat(getOptionValue("foo"), equalTo("not bar"))
+        setOption("foo", "not bar")
+        saveStore()
 
-          setOption("foo", "not bar")
-          saveStore()
-        }
       }
     }
   }
 
-  public Test fun `must be empty if classpath storage`() {
+  public @Test fun `must be empty if classpath storage`() {
     // we must not use VFS here, file must not be created
     val moduleFile = File(tempDirManager.newDirectory("module"), "test.iml")
-    runInEdtAndWait {
-      projectRule.project.runInStoreLoadMode {
-        moduleFile.createModule().useAndDispose {
-          ModuleRootModificationUtil.addContentRoot(this, moduleFile.parentSystemIndependentPath)
-          saveStore()
-          assertThat(moduleFile, anExistingFile())
-          assertThat(moduleFile.readText(), startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"JAVA_MODULE\" version=\"4\">"))
-
-          ClasspathStorage.setStorageType(ModuleRootManager.getInstance(this), "eclipse")
-          saveStore()
-          assertThat(moduleFile.readText(), equalTo("""<?xml version="1.0" encoding="UTF-8"?>
+    projectRule.project.runInStoreLoadMode {
+      moduleFile.createModule().useAndDispose {
+        ModuleRootModificationUtil.addContentRoot(this, moduleFile.parentSystemIndependentPath)
+        saveStore()
+        assertThat(moduleFile, anExistingFile())
+        assertThat(moduleFile.readText(), startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"JAVA_MODULE\" version=\"4\">"))
+
+        ClasspathStorage.setStorageType(ModuleRootManager.getInstance(this), "eclipse")
+        saveStore()
+        assertThat(moduleFile.readText(), equalTo("""<?xml version="1.0" encoding="UTF-8"?>
 <module classpath="eclipse" classpath-dir="$MODULE_DIR" type="JAVA_MODULE" version="4" />"""))
-        }
       }
     }
   }
index f09e339302245663c8abb4364931ae1e897f25a0..e8c05625954b3a6def3e853b5864572c155cbcd2 100644 (file)
@@ -19,11 +19,11 @@ import com.intellij.ide.highlighter.ProjectFileType
 import com.intellij.openapi.application.runWriteAction
 import com.intellij.openapi.components.*
 import com.intellij.openapi.project.Project
+import com.intellij.openapi.project.ProjectManager
 import com.intellij.openapi.project.ex.ProjectManagerEx
 import com.intellij.openapi.project.impl.ProjectManagerImpl
-import com.intellij.openapi.util.Disposer
-import com.intellij.openapi.util.io.FileUtil
 import com.intellij.openapi.util.io.systemIndependentPath
+import com.intellij.openapi.vfs.VirtualFile
 import com.intellij.testFramework.ProjectRule
 import com.intellij.testFramework.RuleChain
 import com.intellij.testFramework.TemporaryDirectory
@@ -42,25 +42,28 @@ fun createProject(tempDirManager: TemporaryDirectory, task: (Project) -> Unit) {
   createOrLoadProject(tempDirManager, task)
 }
 
-fun loadProject(tempDirManager: TemporaryDirectory, directoryBased: Boolean = true, projectCreator: ((File) -> Unit)? = null, task: (Project) -> Unit) {
-  createOrLoadProject(tempDirManager, task, directoryBased, projectCreator)
+fun loadProject(tempDirManager: TemporaryDirectory, projectCreator: ((VirtualFile) -> String)? = null, task: (Project) -> Unit) {
+  createOrLoadProject(tempDirManager, task, projectCreator)
 }
 
-private fun createOrLoadProject(tempDirManager: TemporaryDirectory, task: (Project) -> Unit, directoryBased: Boolean = true, projectCreator: ((File) -> Unit)? = null) {
-  var projectFile = tempDirManager.newDirectory()
-  if (!directoryBased) {
-    projectFile = File(projectFile, "test${ProjectFileType.DOT_DEFAULT_EXTENSION}")
-  }
-
-  projectCreator?.invoke(projectFile)
+private fun createOrLoadProject(tempDirManager: TemporaryDirectory, task: (Project) -> Unit, projectCreator: ((VirtualFile) -> String)? = null) {
   runInEdtAndWait {
-    val projectManager = ProjectManagerEx.getInstanceEx()
-    var project = if (projectCreator == null) (projectManager as ProjectManagerImpl).newProject(null, projectFile.systemIndependentPath, true, false, false)!! else projectManager.loadProject(projectFile.path)!!
+    var filePath: String
+    if (projectCreator == null) {
+      filePath = tempDirManager.newDirectory("test${ProjectFileType.DOT_DEFAULT_EXTENSION}").systemIndependentPath
+    }
+    else {
+      filePath = runWriteAction { projectCreator(tempDirManager.newVirtualDirectory()) }
+    }
+
+    val projectManager = ProjectManagerEx.getInstanceEx() as ProjectManagerImpl
+    var project = if (projectCreator == null) projectManager.newProject(null, filePath, true, false, false)!! else projectManager.loadProject(filePath)!!
     try {
+      projectManager.openTestProject(project)
       task(project)
     }
     finally {
-      runWriteAction { Disposer.dispose(project) }
+      projectManager.closeProject(project, false, true, false)
     }
   }
 }
@@ -99,18 +102,30 @@ class ProjectStoreTest {
   data class TestState(var value: String = "default")
 
   public Test fun directoryBasedStorage() {
-    loadProject(tempDirManager, true, { FileUtil.writeToFile(File(it, Project.DIRECTORY_STORE_FOLDER + "/misc.xml"), iprFileContent) }) {project ->
-      test(project)
+    loadProject(tempDirManager, {
+      it.writeChild("${Project.DIRECTORY_STORE_FOLDER}/misc.xml", iprFileContent)
+      it.path
+    }) { project ->
+      val testComponent = test(project)
+
+      // test reload on external change
+      val file = File(project.stateStore.getStateStorageManager().expandMacros(StoragePathMacros.PROJECT_FILE))
+      file.writeText(file.readText().replace("""<option name="value" value="foo" />""", """<option name="value" value="newValue" />"""))
+
+      project.getBaseDir().refresh(false, true)
+      (ProjectManager.getInstance() as StoreAwareProjectManager).flushChangedAlarm()
+
+      assertThat(testComponent.getState(), equalTo(TestState("newValue")))
     }
   }
 
   public Test fun fileBasedStorage() {
-    loadProject(tempDirManager, false, { FileUtil.writeToFile(it, iprFileContent) }) {project ->
+    loadProject(tempDirManager, { it.writeChild("test${ProjectFileType.DOT_DEFAULT_EXTENSION}", iprFileContent).path }) { project ->
       test(project)
     }
   }
 
-  private fun test(project: Project) {
+  private fun test(project: Project): TestComponent {
     val testComponent = TestComponent()
     project.stateStore.initComponent(testComponent, true)
     assertThat(testComponent.getState(), equalTo(TestState("customValue")))
@@ -123,5 +138,7 @@ class ProjectStoreTest {
     // test exact string - xml prolog, line separators, indentation and so on must be exactly the same
     // todo get rid of default component states here
     assertThat(file.readText(), startsWith(iprFileContent.replace("customValue", "foo").replace("</project>", "")))
+
+    return testComponent
   }
 }
\ No newline at end of file
index 0a151f793263f95b48dfe926d04250f51c83b5ff..2f6d4529a52a9d062e3fae5bf0f07ade8b2e7cc1 100644 (file)
@@ -17,9 +17,9 @@ package com.intellij.configurationStore
 
 import com.intellij.openapi.components.ComponentManager
 import com.intellij.openapi.components.RoamingType
-import com.intellij.openapi.components.impl.stores.StoreUtil
 import com.intellij.openapi.components.stateStore
 import com.intellij.testFramework.ProjectRule
+import com.intellij.util.SmartList
 import junit.framework.TestCase
 import org.hamcrest.CoreMatchers.equalTo
 import org.hamcrest.CoreMatchers.notNullValue
@@ -48,7 +48,7 @@ class StorageManagerTest {
     storageManager.addMacro(MACRO, "/temp/m1")
   }
 
-  public Test fun testCreateFileStateStorageMacroSubstituted() {
+  public Test fun createFileStateStorageMacroSubstituted() {
     assertThat(storageManager.getStateStorage("$MACRO/test.xml", RoamingType.PER_USER), notNullValue())
   }
 
@@ -81,5 +81,5 @@ class StorageManagerTest {
 }
 
 fun ComponentManager.saveStore() {
-  StoreUtil.save(stateStore, null)
+  stateStore.save(SmartList())
 }
\ No newline at end of file
index 921d3a26839df94c62ba8abf7f6caf2ffa2c377f..01ad73ff6d9fa59fa25ebb22f7987cefef360240 100644 (file)
@@ -24,7 +24,7 @@ import org.junit.Test
 class XmlElementStorageTest {
   public Test fun testGetStateSucceeded() {
     val storage = MyXmlElementStorage(tag("root", tag("component", attr("name", "test"), tag("foo"))))
-    val state = storage.getState(this, "test", javaClass<Element>(), null)
+    val state = storage.getState(this, "test", javaClass<Element>())
     TestCase.assertNotNull(state)
     TestCase.assertEquals("component", state.getName())
     TestCase.assertNotNull(state.getChild("foo"))
@@ -32,7 +32,7 @@ class XmlElementStorageTest {
 
   public Test fun testGetStateNotSucceeded() {
     val storage = MyXmlElementStorage(tag("root"))
-    val state = storage.getState(this, "test", javaClass<Element>(), null)
+    val state = storage.getState(this, "test", javaClass<Element>())
     TestCase.assertNull(state)
   }
 
index f1889c5054d97aeaec2b3693bbc17b9293abade0..611fe71b0d703356a34ee593b49f2d6d10774e29 100644 (file)
@@ -30,6 +30,7 @@ import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.util.io.StreamUtil;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.util.*;
+import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.containers.MultiMap;
 import com.intellij.util.execution.ParametersListUtil;
 import com.intellij.util.graph.CachingSemiGraph;
@@ -504,8 +505,9 @@ public class PluginManagerCore {
     return Collections.emptyList();
   }
 
-  private static void prepareLoadingPluginsErrorMessage(@NotNull String errorMessage) {
-    if (!StringUtil.isEmptyOrSpaces(errorMessage)) {
+  private static void prepareLoadingPluginsErrorMessage(@NotNull List<String> errors) {
+    if (!errors.isEmpty()) {
+      String errorMessage = IdeBundle.message("error.problems.found.loading.plugins") + StringUtil.join(errors, "<p/>");
       if (ApplicationManager.getApplication() != null
           && !ApplicationManager.getApplication().isHeadlessEnvironment()
           && !ApplicationManager.getApplication().isUnitTestMode()) {
@@ -529,23 +531,40 @@ public class PluginManagerCore {
   }
 
   @NotNull
-  private static Comparator<IdeaPluginDescriptor> getPluginDescriptorComparator(@NotNull final Map<PluginId, ? extends IdeaPluginDescriptor> idToDescriptorMap) {
+  private static Comparator<IdeaPluginDescriptor> getPluginDescriptorComparator(@NotNull final Map<PluginId, ? extends IdeaPluginDescriptor> idToDescriptorMap, @NotNull List<String> errors) {
     final Graph<PluginId> graph = createPluginIdGraph(idToDescriptorMap);
     final DFSTBuilder<PluginId> builder = new DFSTBuilder<PluginId>(graph);
     if (!builder.isAcyclic()) {
+      final List<String> cycles = new ArrayList<String>();
       builder.getSCCs().forEach(new TIntProcedure() {
         private int myTNumber;
         @Override
         public boolean execute(int size) {
           if (size > 1) {
+            String cycle = "";
             for (int j = 0; j < size; j++) {
-              idToDescriptorMap.get(builder.getNodeByTNumber(myTNumber + j)).setEnabled(false);
+              PluginId id = builder.getNodeByTNumber(myTNumber + j);
+              idToDescriptorMap.get(id).setEnabled(false);
+              cycle += id.getIdString() + " ";
             }
+            cycles.add(cycle);
           }
           myTNumber += size;
           return true;
         }
       });
+
+      final String cyclePresentation;
+      if (ApplicationManager.getApplication().isInternal()) {
+        cyclePresentation = StringUtil.join(cycles, ";");
+      }
+      else {
+        final Couple<PluginId> circularDependency = builder.getCircularDependency();
+        final PluginId id = circularDependency.getFirst();
+        final PluginId parentId = circularDependency.getSecond();
+        cyclePresentation = id + "->" + parentId + "->...->" + id;
+      }
+      errors.add(IdeBundle.message("error.plugins.should.not.have.cyclic.dependencies") + " " + cyclePresentation);
     }
 
     final Comparator<PluginId> idComparator = builder.comparator();
@@ -605,7 +624,7 @@ public class PluginManagerCore {
       }
       catch (XmlSerializationException e) {
         getLogger().info("Cannot load " + file, e);
-        prepareLoadingPluginsErrorMessage("File '" + file.getName() + "' contains invalid plugin descriptor.");
+        prepareLoadingPluginsErrorMessage(Collections.singletonList("File '" + file.getName() + "' contains invalid plugin descriptor."));
       }
       catch (Throwable e) {
         getLogger().info("Cannot load " + file, e);
@@ -637,7 +656,7 @@ public class PluginManagerCore {
     }
     catch (XmlSerializationException e) {
       getLogger().info("Cannot load " + file, e);
-      prepareLoadingPluginsErrorMessage("File '" + file.getName() + "' contains invalid plugin descriptor.");
+      prepareLoadingPluginsErrorMessage(Collections.singletonList("File '" + file.getName() + "' contains invalid plugin descriptor."));
     }
     catch (Throwable e) {
       getLogger().info("Cannot load " + file, e);
@@ -785,10 +804,8 @@ public class PluginManagerCore {
     }
   }
 
-  @NotNull
-  private static String filterBadPlugins(@NotNull List<? extends IdeaPluginDescriptor> result, @NotNull final Map<String, String> disabledPluginNames) {
+  private static void filterBadPlugins(@NotNull List<? extends IdeaPluginDescriptor> result, @NotNull final Map<String, String> disabledPluginNames, @NotNull final List<String> errors) {
     final Map<PluginId, IdeaPluginDescriptor> idToDescriptorMap = new THashMap<PluginId, IdeaPluginDescriptor>();
-    final StringBuilder message = new StringBuilder();
     boolean pluginsWithoutIdFound = false;
     for (Iterator<? extends IdeaPluginDescriptor> it = result.iterator(); it.hasNext();) {
       final IdeaPluginDescriptor descriptor = it.next();
@@ -797,9 +814,7 @@ public class PluginManagerCore {
         pluginsWithoutIdFound = true;
       }
       else if (idToDescriptorMap.containsKey(id)) {
-        message.append("<br>");
-        message.append(IdeBundle.message("message.duplicate.plugin.id"));
-        message.append(id);
+        errors.add(IdeBundle.message("message.duplicate.plugin.id") + id);
         it.remove();
       }
       else if (descriptor.isEnabled()) {
@@ -824,7 +839,6 @@ public class PluginManagerCore {
             if (!pluginId.getIdString().startsWith(MODULE_DEPENDENCY_PREFIX)) {
               faultyDescriptors.add(pluginId.getIdString());
               disabledPluginIds.add(pluginDescriptor.getPluginId().getIdString());
-              message.append("<br>");
               final String name = pluginDescriptor.getName();
               final IdeaPluginDescriptor descriptor = idToDescriptorMap.get(pluginId);
               String pluginName;
@@ -838,7 +852,7 @@ public class PluginManagerCore {
                 pluginName = descriptor.getName();
               }
 
-              message.append(getDisabledPlugins().contains(pluginId.getIdString())
+              errors.add(getDisabledPlugins().contains(pluginId.getIdString())
                              ? IdeBundle.message("error.required.plugin.disabled", name, pluginName)
                              : IdeBundle.message("error.required.plugin.not.installed", name, pluginName));
             }
@@ -852,16 +866,15 @@ public class PluginManagerCore {
     if (!disabledPluginIds.isEmpty()) {
       myPlugins2Disable = disabledPluginIds;
       myPlugins2Enable = faultyDescriptors;
-      message.append("<br>");
-      message.append("<br>").append("<a href=\"" + DISABLE + "\">Disable ");
+      String error = "<br><a href=\"" + DISABLE + "\">Disable ";
       if (disabledPluginIds.size() == 1) {
         final PluginId pluginId2Disable = PluginId.getId(disabledPluginIds.iterator().next());
-        message.append(idToDescriptorMap.containsKey(pluginId2Disable) ? idToDescriptorMap.get(pluginId2Disable).getName() : pluginId2Disable.getIdString());
+        error += idToDescriptorMap.containsKey(pluginId2Disable) ? idToDescriptorMap.get(pluginId2Disable).getName() : pluginId2Disable.getIdString();
       }
       else {
-        message.append("not loaded plugins");
+        error +="not loaded plugins";
       }
-      message.append("</a>");
+      errors.add(error + "</a>");
       boolean possibleToEnable = true;
       for (String descriptor : faultyDescriptors) {
         if (disabledPluginNames.get(descriptor) == null) {
@@ -870,19 +883,13 @@ public class PluginManagerCore {
         }
       }
       if (possibleToEnable) {
-        message.append("<br>").append("<a href=\"" + ENABLE + "\">Enable ").append(faultyDescriptors.size() == 1 ? disabledPluginNames.get(faultyDescriptors.iterator().next()) : " all necessary plugins").append("</a>");
+        errors.add("<a href=\"" + ENABLE + "\">Enable " + (faultyDescriptors.size() == 1 ? disabledPluginNames.get(faultyDescriptors.iterator().next()) : " all necessary plugins") + "</a>");
       }
-      message.append("<br>").append("<a href=\"" + EDIT + "\">Open plugin manager</a>");
+      errors.add("<a href=\"" + EDIT + "\">Open plugin manager</a>");
     }
     if (pluginsWithoutIdFound) {
-      message.append("<br>");
-      message.append(IdeBundle.message("error.plugins.without.id.found"));
+      errors.add(IdeBundle.message("error.plugins.without.id.found"));
     }
-    if (message.length() > 0) {
-      message.insert(0, IdeBundle.message("error.problems.found.loading.plugins"));
-      return message.toString();
-    }
-    return "";
   }
 
   public static void loadDescriptorsFromClassPath(@NotNull List<IdeaPluginDescriptorImpl> result,
@@ -968,7 +975,7 @@ public class PluginManagerCore {
   }
 
   @NotNull
-  public static IdeaPluginDescriptorImpl[] loadDescriptors(@Nullable StartupProgress progress) {
+  public static IdeaPluginDescriptorImpl[] loadDescriptors(@Nullable StartupProgress progress, @NotNull List<String> errors) {
     if (ClassUtilCore.isLoadingOfExternalPluginsDisabled()) {
       return IdeaPluginDescriptorImpl.EMPTY_ARRAY;
     }
@@ -989,18 +996,18 @@ public class PluginManagerCore {
 
     loadDescriptorsFromClassPath(result, getClassLoaderUrls(), fromSources ? progress : null);
 
-    return topoSortPlugins(result);
+    return topoSortPlugins(result, errors);
   }
 
   @NotNull // used in upsource
-  public static IdeaPluginDescriptorImpl[] topoSortPlugins(@NotNull List<IdeaPluginDescriptorImpl> result) {
+  public static IdeaPluginDescriptorImpl[] topoSortPlugins(@NotNull List<IdeaPluginDescriptorImpl> result, @NotNull List<String> errors) {
     IdeaPluginDescriptorImpl[] pluginDescriptors = result.toArray(new IdeaPluginDescriptorImpl[result.size()]);
     final Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap = new THashMap<PluginId, IdeaPluginDescriptorImpl>();
     for (IdeaPluginDescriptorImpl descriptor : pluginDescriptors) {
       idToDescriptorMap.put(descriptor.getPluginId(), descriptor);
     }
 
-    Arrays.sort(pluginDescriptors, getPluginDescriptorComparator(idToDescriptorMap));
+    Arrays.sort(pluginDescriptors, getPluginDescriptorComparator(idToDescriptorMap, errors));
     return pluginDescriptors;
   }
 
@@ -1131,7 +1138,8 @@ public class PluginManagerCore {
   private static void initializePlugins(@Nullable StartupProgress progress) {
     configureExtensions();
 
-    final IdeaPluginDescriptorImpl[] pluginDescriptors = loadDescriptors(progress);
+    final List<String> errors = ContainerUtil.newArrayList();
+    final IdeaPluginDescriptorImpl[] pluginDescriptors = loadDescriptors(progress, errors);
 
     final Class callerClass = ReflectionUtil.findCallerClass(1);
     assert callerClass != null;
@@ -1141,46 +1149,12 @@ public class PluginManagerCore {
     final Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap = new THashMap<PluginId, IdeaPluginDescriptorImpl>();
     final Map<String, String> disabledPluginNames = new THashMap<String, String>();
     List<String> brokenPluginsList = new SmartList<String>();
-    String errorMessage =
-      fixDescriptors(pluginDescriptors, parentLoader, idToDescriptorMap, disabledPluginNames, brokenPluginsList, result);
+    fixDescriptors(pluginDescriptors, parentLoader, idToDescriptorMap, disabledPluginNames, brokenPluginsList, result, errors);
 
     final Graph<PluginId> graph = createPluginIdGraph(idToDescriptorMap);
     final DFSTBuilder<PluginId> builder = new DFSTBuilder<PluginId>(graph);
-    if (!builder.isAcyclic()) {
-      if (!StringUtil.isEmptyOrSpaces(errorMessage)) {
-        errorMessage += "<br>";
-      }
-
-      final String cyclePresentation;
-      if (ApplicationManager.getApplication().isInternal()) {
-        final List<String> cycles = new ArrayList<String>();
-        builder.getSCCs().forEach(new TIntProcedure() {
-          private int myTNumber;
-          @Override
-          public boolean execute(int size) {
-            if (size > 1) {
-              String cycle = "";
-              for (int j = 0; j < size; j++) {
-                cycle += builder.getNodeByTNumber(myTNumber + j).getIdString() + " ";
-              }
-              cycles.add(cycle);
-            }
-            myTNumber += size;
-            return true;
-          }
-        });
-        cyclePresentation = ": " + StringUtil.join(cycles, ";");
-      }
-      else {
-        final Couple<PluginId> circularDependency = builder.getCircularDependency();
-        final PluginId id = circularDependency.getFirst();
-        final PluginId parentId = circularDependency.getSecond();
-        cyclePresentation = id + "->" + parentId + "->...->" + id;
-      }
-      errorMessage += IdeBundle.message("error.plugins.should.not.have.cyclic.dependencies") + cyclePresentation;
-    }
 
-    prepareLoadingPluginsErrorMessage(errorMessage);
+    prepareLoadingPluginsErrorMessage(errors);
 
     final Comparator<PluginId> idComparator = builder.comparator();
     // sort descriptors according to plugin dependencies
@@ -1231,27 +1205,23 @@ public class PluginManagerCore {
     ourPlugins = pluginDescriptors;
   }
 
-  @NotNull // used in upsource
-  public static String fixDescriptors(@NotNull IdeaPluginDescriptorImpl[] pluginDescriptors,
+  // used in upsource
+  public static void fixDescriptors(@NotNull IdeaPluginDescriptorImpl[] pluginDescriptors,
                                       @NotNull ClassLoader parentLoader,
                                       @NotNull Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap,
                                       @NotNull Map<String, String> disabledPluginNames,
                                       @NotNull List<String> brokenPluginsList,
-                                      @NotNull List<IdeaPluginDescriptorImpl> result) {
+                                      @NotNull List<IdeaPluginDescriptorImpl> result,
+                                      @NotNull List<String> errors) {
     checkCanLoadPlugins(pluginDescriptors, parentLoader, disabledPluginNames, brokenPluginsList, result);
 
-    String errorMessage = filterBadPlugins(result, disabledPluginNames);
+    filterBadPlugins(result, disabledPluginNames, errors);
 
     if (!brokenPluginsList.isEmpty()) {
-      if (!StringUtil.isEmptyOrSpaces(errorMessage)) {
-        errorMessage += "<br>";
-      }
-      errorMessage += "Following plugins are incompatible with current IDE build: " + StringUtil.join(brokenPluginsList, ", ")
-                      + "<br>\n" + StringUtil.notNullize(errorMessage);
+      errors.add("Following plugins are incompatible with current IDE build: " + StringUtil.join(brokenPluginsList, ", "));
     }
 
     fixDependencies(result,idToDescriptorMap);
-    return errorMessage;
   }
 
   private static void checkCanLoadPlugins(@NotNull IdeaPluginDescriptorImpl[] pluginDescriptors,
index f71e1cea89839f5c62e8ee3c5b7625b397c3735c..dcea9bf4202dd1bfdf148179ae2493ac570eb91c 100644 (file)
@@ -621,7 +621,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen
 
   @Override
   public void beforeDocumentChange(@NotNull DocumentEvent event) {
-    if (myStopTrackingDocuments) return;
+    if (myStopTrackingDocuments || myProject.isDisposed()) return;
 
     final Document document = event.getDocument();
     if (document instanceof DocumentImpl && !myUncommittedInfos.containsKey(document)) {
@@ -661,7 +661,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen
 
   @Override
   public void documentChanged(DocumentEvent event) {
-    if (myStopTrackingDocuments) return;
+    if (myStopTrackingDocuments || myProject.isDisposed()) return;
 
     final Document document = event.getDocument();
     if (document instanceof DocumentImpl) {
index 8544671b4aaec048178f662bedbd1eec52ce0997..c6fd62c1ca687c7ef9b7e49b9dd5c78737cdbf4a 100644 (file)
@@ -287,8 +287,11 @@ public class InspectionProfileManagerImpl extends InspectionProfileManager imple
   @Override
   public void setRootProfile(String rootProfile) {
     Profile current = mySchemesManager.getCurrentScheme();
-    if (current != null && !Comparing.strEqual(rootProfile, current.getName())) {
+    if (current == null || !Comparing.strEqual(rootProfile, current.getName())) {
       Profile scheme = getProfile(rootProfile);
+      if (scheme == null && current == null) {
+        return;
+      }
       fireProfileChanged(current, scheme, null);
       mySchemesManager.setCurrent(scheme, false);
     }
index ad841c1d1c986728f29c87743cd688587ecb6d34..8d0d64f332ad4fc2d8ef0b7c9605ff5cd176f5c2 100644 (file)
@@ -394,6 +394,7 @@ public class ExecutionManagerImpl extends ExecutionManager implements Disposable
               started = true;
               processHandler.addProcessListener(new ProcessExecutionListener(project, profile, processHandler));
             }
+            environment.setContentToReuse(descriptor);
           }
         }
         catch (ProcessCanceledException e) {
index 198d50920a5c35adfc57df2df8a85f7d1453b03b..ce2a531fca3c7b976ce39a2567d1ba6cfd17a179 100644 (file)
@@ -30,7 +30,9 @@ import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter;
 import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.project.ProjectLocator;
+import com.intellij.openapi.project.impl.ProjectImpl;
 import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.pom.core.impl.PomModelImpl;
 import com.intellij.psi.FileViewProvider;
@@ -110,9 +112,11 @@ public class PsiDocumentManagerImpl extends PsiDocumentManagerBase implements Se
       if (myUnitTestMode) {
         myStopTrackingDocuments = true;
         try {
-          LOG.error("Too many uncommitted documents for " + myProject + ":\n" + myUncommittedDocuments);
+          LOG.error("Too many uncommitted documents for " + myProject + ":\n" + StringUtil.join(myUncommittedDocuments, "\n") + 
+                    "\n\n Project creation trace: " + myProject.getUserData(ProjectImpl.CREATION_TRACE));
         }
         finally {
+          //noinspection TestOnlyProblems
           clearUncommittedDocuments();
         }
       }
index 6ba83abb53cc604e6bce9b0542fba2c3634801a0..6e0d5fd0a1eaa49d5623680c8ed3b19ab42a6e4d 100644 (file)
@@ -31,12 +31,10 @@ import com.intellij.openapi.fileTypes.FileType;
 import com.intellij.openapi.fileTypes.LanguageFileType;
 import com.intellij.openapi.progress.ProgressManager;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.util.LowMemoryWatcher;
 import com.intellij.openapi.util.NotNullComputable;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.vfs.newvfs.ManagingFS;
-import com.intellij.openapi.vfs.newvfs.persistent.FlushingDaemon;
 import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
 import com.intellij.psi.LanguageSubstitutors;
 import com.intellij.psi.PsiDocumentManager;
@@ -61,7 +59,6 @@ import org.jetbrains.annotations.Nullable;
 
 import java.io.*;
 import java.util.*;
-import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.locks.Lock;
 
@@ -76,20 +73,8 @@ public class StubIndexImpl extends StubIndex implements ApplicationComponent, Pe
   private final TObjectIntHashMap<ID<?, ?>> myIndexIdToVersionMap = new TObjectIntHashMap<ID<?, ?>>();
 
   private final StubProcessingHelper myStubProcessingHelper;
-  private final ScheduledFuture<?> myPeriodicFlushing = FlushingDaemon.everyFiveSeconds(new Runnable() {
-    @Override
-    public void run() {
-      flushAllStubIndices();
-    }
-  });
 
   private StubIndexState myPreviouslyRegistered;
-  private final LowMemoryWatcher myLowMemoryFlusher = LowMemoryWatcher.register(new Runnable() {
-    @Override
-    public void run() {
-      flushAllStubIndices();
-    }
-  });
 
   public StubIndexImpl(FileBasedIndex fileBasedIndex /* need this to ensure initialization order*/ ) throws IOException {
     final boolean forceClean = Boolean.TRUE == ourForcedClean.getAndSet(Boolean.FALSE);
@@ -122,17 +107,6 @@ public class StubIndexImpl extends StubIndex implements ApplicationComponent, Pe
     myStubProcessingHelper = new StubProcessingHelper(fileBasedIndex);
   }
 
-  private void flushAllStubIndices() {
-    try {
-      for (StubIndexKey key : getAllStubIndexKeys()) {
-        flush(key);
-      }
-    } catch (StorageException e) {
-      LOG.info(e);
-      FileBasedIndex.getInstance().requestRebuild(StubUpdatingIndex.INDEX_ID);
-    }
-  }
-
   @Nullable
   public static StubIndexImpl getInstanceOrInvalidate() {
     if (ourForcedClean.compareAndSet(null, Boolean.TRUE)) {
@@ -421,8 +395,6 @@ public class StubIndexImpl extends StubIndex implements ApplicationComponent, Pe
   }
 
   public void dispose() {
-    myLowMemoryFlusher.stop();
-    myPeriodicFlushing.cancel(false);
     for (UpdatableIndex index : myIndices.values()) {
       index.dispose();
     }
index 03732f754b78ae005aadd7db0423df11b9a06e70..d065c43610482be44eed6e3a8d05c51db020b0c6 100644 (file)
@@ -274,7 +274,7 @@ public class StubUpdatingIndex extends CustomImplementationFileBasedIndexExtensi
     return allIndices;
   }
 
-  private class MyIndex extends MapReduceIndex<Integer, SerializedStubTree, FileContent> {
+  private static class MyIndex extends MapReduceIndex<Integer, SerializedStubTree, FileContent> {
     private StubIndexImpl myStubIndex;
 
     public MyIndex(final ID<Integer, SerializedStubTree> indexId, final IndexStorage<Integer, SerializedStubTree> storage, final DataIndexer<Integer, SerializedStubTree, FileContent> indexer)
@@ -283,6 +283,19 @@ public class StubUpdatingIndex extends CustomImplementationFileBasedIndexExtensi
       checkNameStorage();
     }
 
+    @Override
+    public void flush() throws StorageException {
+      final StubIndexImpl stubIndex = getStubIndex();
+      try {
+        for (StubIndexKey key : stubIndex.getAllStubIndexKeys()) {
+          stubIndex.flush(key);
+        }
+      }
+      finally {
+        super.flush();
+      }
+    }
+
     @Override
     protected void updateWithMap(final int inputId,
                                  @NotNull UpdateData<Integer, SerializedStubTree> updateData)
@@ -341,7 +354,7 @@ public class StubUpdatingIndex extends CustomImplementationFileBasedIndexExtensi
       return index;
     }
 
-    private void checkNameStorage() throws StorageException {
+    private static void checkNameStorage() throws StorageException {
       final SerializationManagerEx serializationManager = SerializationManagerEx.getInstanceEx();
       if (serializationManager.isNameStorageCorrupted()) {
         serializationManager.repairNameStorage();
@@ -350,7 +363,7 @@ public class StubUpdatingIndex extends CustomImplementationFileBasedIndexExtensi
       }
     }
 
-    private Map<StubIndexKey, Map<Object, StubIdList>> getStubTree(@NotNull final Map<Integer, SerializedStubTree> data)
+    private static Map<StubIndexKey, Map<Object, StubIdList>> getStubTree(@NotNull final Map<Integer, SerializedStubTree> data)
       throws SerializerNotFoundException {
       final Map<StubIndexKey, Map<Object, StubIdList>> stubTree;
       if (!data.isEmpty()) {
index 31834dd321167cc7f52403521b3a0f2b52ea778c..063ec0908ada05fdce60ff24b0515486a1fd6ff7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * Copyright 2000-2015 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.
 package com.intellij.application.options.pathMacros;
 
 import com.intellij.openapi.application.ApplicationBundle;
+import com.intellij.openapi.components.ComponentsPackage;
+import com.intellij.openapi.components.impl.stores.StorageUtil;
 import com.intellij.openapi.options.Configurable;
 import com.intellij.openapi.options.ConfigurationException;
 import com.intellij.openapi.options.SearchableConfigurable;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.project.ProjectManager;
-import com.intellij.openapi.project.ex.ProjectEx;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -46,9 +47,8 @@ public class PathMacroConfigurable implements SearchableConfigurable, Configurab
   public void apply() throws ConfigurationException {
     myEditor.commit();
 
-    final Project[] projects = ProjectManager.getInstance().getOpenProjects();
-    for (Project project : projects) {
-      ((ProjectEx)project).checkUnknownMacros(false);
+    for (Project project : ProjectManager.getInstance().getOpenProjects()) {
+      StorageUtil.checkUnknownMacros(ComponentsPackage.getStateStore(project), project, false);
     }
   }
 
index aa8ed4298f65705be15f482af8bdb643c001a602..65e4f30a65e28f6b7d1abfa08fb14eceb6e0b4d4 100644 (file)
@@ -32,6 +32,8 @@ import org.jetbrains.annotations.Nullable;
 import javax.swing.*;
 import javax.swing.event.HyperlinkEvent;
 import java.awt.*;
+import java.util.Collections;
+import java.util.List;
 
 import static com.intellij.notification.NotificationDisplayType.STICKY_BALLOON;
 
@@ -94,9 +96,14 @@ public abstract class ExecutableValidator {
    * @return true if process with the supplied executable completed without errors and with exit code 0.
    */
   protected boolean isExecutableValid(@NotNull String executable) {
+    return doCheckExecutable(executable, Collections.<String>emptyList());
+  }
+
+  protected static boolean doCheckExecutable(@NotNull String executable, @NotNull List<String> processParameters) {
     try {
       GeneralCommandLine commandLine = new GeneralCommandLine();
       commandLine.setExePath(executable);
+      commandLine.addParameters(processParameters);
       CapturingProcessHandler handler = new CapturingProcessHandler(commandLine.createProcess(), CharsetToolkit.getDefaultSystemCharset());
       ProcessOutput result = handler.runProcess(60 * 1000);
       boolean timeout = result.isTimeout();
@@ -106,7 +113,7 @@ public abstract class ExecutableValidator {
         LOG.warn("Validation of " + executable + " failed with a timeout");
       }
       if (exitCode != 0) {
-        LOG.warn("Validation of " + executable + " failed with non-zero exit code: " + exitCode);
+        LOG.warn("Validation of " + executable + " failed with non-zero exit code: " + exitCode);
       }
       if (!stderr.isEmpty()) {
         LOG.warn("Validation of " + executable + " failed with a non-empty error output: " + stderr);
index 1cd96892debd5e3432e28cf2b75a4dedc516cc29..933290e37af2ef1ef8b0b4ad8d99622838d8e2d0 100644 (file)
@@ -53,7 +53,7 @@ public class PluginGroups {
   private Runnable myLoadingCallback = null;
 
   public PluginGroups() {
-    myAllPlugins = PluginManagerCore.loadDescriptors(null);
+    myAllPlugins = PluginManagerCore.loadDescriptors(null, ContainerUtil.<String>newArrayList());
     SwingWorker worker = new SwingWorker<List<IdeaPluginDescriptor>, Object>() {
       @Override
       protected List<IdeaPluginDescriptor> doInBackground() throws Exception {
index c309097658f889d39dbd80b76e23fabe6c7c88ee..717cb69bfd87d8ff62dfa5ea98b4a8ce17bf634b 100644 (file)
@@ -24,6 +24,7 @@ import com.intellij.openapi.application.ex.ApplicationInfoEx;
 import com.intellij.openapi.extensions.PluginId;
 import com.intellij.ui.wizard.WizardModel;
 import com.intellij.util.ArrayUtil;
+import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.containers.HashSet;
 import com.intellij.util.containers.MultiMap;
 import org.jetbrains.annotations.Nullable;
@@ -59,7 +60,7 @@ public class StartupWizardModel extends WizardModel {
       add(myOtherStep);
     }
 
-    myAllPlugins = PluginManager.loadDescriptors(null);
+    myAllPlugins = PluginManager.loadDescriptors(null, ContainerUtil.<String>newArrayList());
     for (IdeaPluginDescriptor pluginDescriptor : myAllPlugins) {
       if (pluginDescriptor.getPluginId().getIdString().equals("com.intellij")) {
         // skip 'IDEA CORE' plugin
index 1d8d21a58f48679aeefee194a7f61a5dc71ad62a..a136538b7c4d25c583bf8a43a46827fd470ec4a1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2014 JetBrains s.r.o.
+ * Copyright 2000-2015 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.
@@ -18,9 +18,7 @@ package com.intellij.ide.ui;
 import com.intellij.ide.IdeBundle;
 import com.intellij.ide.ui.laf.darcula.DarculaInstaller;
 import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.components.*;
-import com.intellij.openapi.components.impl.stores.IComponentStore;
-import com.intellij.openapi.components.impl.stores.StateStorageManager;
+import com.intellij.openapi.components.ComponentsPackage;
 import com.intellij.openapi.editor.colors.ex.DefaultColorSchemesManager;
 import com.intellij.openapi.editor.ex.util.EditorUtil;
 import com.intellij.openapi.options.BaseConfigurable;
@@ -40,7 +38,6 @@ import javax.swing.event.ChangeListener;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.util.Dictionary;
-import java.util.HashSet;
 import java.util.Hashtable;
 
 /**
@@ -207,7 +204,7 @@ public class AppearanceConfigurable extends BaseConfigurable implements Searchab
     if (settings.COLOR_BLINDNESS != blindness) {
       settings.COLOR_BLINDNESS = blindness;
       update = true;
-      reloadComponent(DefaultColorSchemesManager.class);
+      ComponentsPackage.getStateStore(ApplicationManager.getApplication()).reloadState(DefaultColorSchemesManager.class);
       shouldUpdateUI = true;
     }
 
@@ -481,17 +478,4 @@ public class AppearanceConfigurable extends BaseConfigurable implements Searchab
   public Runnable enableSearch(String option) {
     return null;
   }
-
-  private static void reloadComponent(Class<?> type) {
-    State state = type.getAnnotation(State.class);
-    if (state != null) {
-      IComponentStore store = ComponentsPackage.getStateStore(ApplicationManager.getApplication());
-      StateStorageManager manager = store.getStateStorageManager();
-      HashSet<StateStorage> storages = new HashSet<StateStorage>();
-      for (Storage storage : state.storages()) {
-        storages.add(manager.getStateStorage(storage));
-      }
-      store.reinitComponent(state.name(), storages);
-    }
-  }
 }
index ce74a828eb3209ce3067f9b958f789ee1488f511..22b312d3974f4693515b4fa0d8392631a550ab1e 100644 (file)
  */
 package com.intellij.openapi.components.impl.stores;
 
-import com.intellij.openapi.components.StateStorage;
 import com.intellij.openapi.vfs.VirtualFile;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.io.File;
 
-public interface FileStorage extends StateStorage {
+public interface FileStorage {
   @Nullable
   VirtualFile getVirtualFile();
 
   @NotNull
   File getFile();
 
-  // todo remove
-  StorageDataBase getStorageData();
-
   // todo remove
   void setFile(@Nullable VirtualFile file, @Nullable File ioFileIfChanged);
 }
\ No newline at end of file
index dda2c2fd0967502d1cdf6c00bd641d6f442491dc..702a011a4e56042370ea93ce92e88feb1ba4ba49 100644 (file)
  */
 package com.intellij.openapi.components.impl.stores;
 
+import com.intellij.openapi.components.PersistentStateComponent;
 import com.intellij.openapi.components.StateStorage;
 import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.vfs.VirtualFile;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 import org.jetbrains.annotations.TestOnly;
 
 import java.util.Collection;
@@ -34,9 +34,9 @@ public interface IComponentStore {
 
   void initComponent(@NotNull Object component, boolean service);
 
-  void reinitComponents(@NotNull Set<String> componentNames, boolean reloadData);
+  void reloadStates(@NotNull Set<String> componentNames);
 
-  boolean reinitComponent(@NotNull String componentName, @NotNull Set<StateStorage> changedStorages);
+  void reloadState(@NotNull Class<? extends PersistentStateComponent<?>> componentClass);
 
   @NotNull
   Collection<String> getNotReloadableComponents(@NotNull Collection<String> componentNames);
@@ -57,14 +57,6 @@ public interface IComponentStore {
 
   void save(@NotNull List<Pair<StateStorage.SaveSession, VirtualFile>> readonlyFiles);
 
-  /**
-   * null if reloaded
-   * empty list if nothing to reload
-   * list of not reloadable components (reload is not performed)
-   */
-  @Nullable
-  Collection<String> reload(@NotNull Set<StateStorage> changedStorages);
-
   @TestOnly
   void saveApplicationComponent(@NotNull Object component);
 }
index 4e212a3b3e8f3a87ce4c577ef0b19cec52d6197e..fde42957abeceaabe6fe60104606626878f6bfb8 100644 (file)
@@ -32,8 +32,13 @@ public abstract class StateStorageBase<T extends StorageDataBase> implements Sta
 
   @Override
   @Nullable
-  public final <S> S getState(Object component, @NotNull String componentName, @NotNull Class<S> stateClass, @Nullable S mergeInto) {
-    return deserializeState(getStateAndArchive(getStorageData(), component, componentName), stateClass, mergeInto);
+  public final <S> S getState(Object component, @NotNull String componentName, @NotNull Class<S> stateClass, @Nullable S mergeInto, boolean reload) {
+    return deserializeState(getStateAndArchive(getStorageData(reload), component, componentName), stateClass, mergeInto);
+  }
+
+  @Override
+  public final <S> S getState(@Nullable Object component, @NotNull String componentName, @NotNull Class<S> stateClass) {
+    return getState(component, componentName, stateClass, null, false);
   }
 
   @Nullable
index dc631ec0a3f274e51162cc392f861b4549bd73f0..ba5c476962cf48a36e26291041311b911e3ac906 100644 (file)
 package com.intellij.openapi.components.impl.stores;
 
 import com.intellij.openapi.components.*;
-import com.intellij.openapi.util.Couple;
 import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
 import com.intellij.util.messages.Topic;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.Collection;
 import java.util.List;
 
 public interface StateStorageManager {
@@ -37,9 +35,6 @@ public interface StateStorageManager {
   @NotNull
   StateStorage getStateStorage(@NotNull String fileSpec, @NotNull RoamingType roamingType);
 
-  @NotNull
-  Couple<Collection<FileStorage>> getCachedFileStateStorages(@NotNull Collection<String> changed, @NotNull Collection<String> deleted);
-
   /**
    * Rename file
    * @param path System-independent full old path (/project/bar.iml or collapse $MODULE_FILE$)
index 428783737fb8d16a8855459c374d1ef40b8a3f27..78648f613c585613cb8c9dac7745040930a6ffa5 100644 (file)
@@ -26,8 +26,10 @@ import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.project.ProjectBundle;
-import com.intellij.openapi.project.ex.ProjectEx;
+import com.intellij.openapi.project.ex.ProjectManagerEx;
+import com.intellij.openapi.project.impl.ProjectMacrosUtil;
 import com.intellij.openapi.startup.StartupManager;
+import com.intellij.openapi.ui.Messages;
 import com.intellij.openapi.util.JDOMUtil;
 import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
@@ -37,12 +39,13 @@ import com.intellij.openapi.vfs.CharsetToolkit;
 import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VfsUtil;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.AppUIUtil;
 import com.intellij.util.ArrayUtil;
 import com.intellij.util.LineSeparator;
 import com.intellij.util.SmartList;
 import com.intellij.util.SystemProperties;
 import com.intellij.util.containers.ContainerUtil;
-import com.intellij.util.ui.UIUtil;
+import gnu.trove.THashSet;
 import org.jdom.Element;
 import org.jdom.Parent;
 import org.jetbrains.annotations.NotNull;
@@ -55,8 +58,7 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
-import java.util.LinkedHashSet;
-import java.util.List;
+import java.util.*;
 
 public class StorageUtil {
   public static final String DEFAULT_EXT = ".xml";
@@ -74,29 +76,31 @@ public class StorageUtil {
 
   public static void checkUnknownMacros(@NotNull final ComponentManager componentManager, @NotNull final Project project) {
     Application application = ApplicationManager.getApplication();
-    if (!application.isHeadlessEnvironment() && !application.isUnitTestMode()) {
-      // should be invoked last
-      StartupManager.getInstance(project).runWhenProjectIsInitialized(new Runnable() {
-        @Override
-        public void run() {
-          TrackingPathMacroSubstitutor substitutor = ComponentsPackage.getStateStore(componentManager).getStateStorageManager().getMacroSubstitutor();
-          if (substitutor != null) {
-            notifyUnknownMacros(substitutor, project, null);
-          }
-        }
-      });
+    if (application.isHeadlessEnvironment() || application.isUnitTestMode()) {
+      return;
     }
+
+    // should be invoked last
+    StartupManager.getInstance(project).runWhenProjectIsInitialized(new Runnable() {
+      @Override
+      public void run() {
+        notifyUnknownMacros(ComponentsPackage.getStateStore(componentManager), project, null);
+      }
+    });
   }
 
-  public static void notifyUnknownMacros(@NotNull TrackingPathMacroSubstitutor substitutor,
-                                         @NotNull final Project project,
-                                         @Nullable final String componentName) {
+  public static void notifyUnknownMacros(@NotNull final IComponentStore store, @NotNull final Project project, @Nullable final String componentName) {
+    TrackingPathMacroSubstitutor substitutor = store.getStateStorageManager().getMacroSubstitutor();
+    if (substitutor == null) {
+      return;
+    }
+
     final LinkedHashSet<String> macros = new LinkedHashSet<String>(substitutor.getUnknownMacros(componentName));
     if (macros.isEmpty()) {
       return;
     }
 
-    UIUtil.invokeLaterIfNeeded(new Runnable() {
+    AppUIUtil.invokeOnEdt(new Runnable() {
       @Override
       public void run() {
         List<String> notified = null;
@@ -111,26 +115,82 @@ public class StorageUtil {
           macros.removeAll(notified);
         }
 
-        if (!macros.isEmpty()) {
-          LOG.debug("Reporting unknown path macros " + macros + " in component " + componentName);
-          String format = "<p><i>%s</i> %s undefined. <a href=\"define\">Fix it</a></p>";
-          String productName = ApplicationNamesInfo.getInstance().getProductName();
-          String content = String.format(format, StringUtil.join(macros, ", "), macros.size() == 1 ? "is" : "are") +
-                           "<br>Path variables are used to substitute absolute paths " +
-                           "in " + productName + " project files " +
-                           "and allow project file sharing in version control systems.<br>" +
-                           "Some of the files describing the current project settings contain unknown path variables " +
-                           "and " + productName + " cannot restore those paths.";
-          new UnknownMacroNotification("Load Error", "Load error: undefined path variables", content, NotificationType.ERROR,
-                                       new NotificationListener() {
-                                         @Override
-                                         public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
-                                           ((ProjectEx)project).checkUnknownMacros(true);
-                                         }
-                                       }, macros).notify(project);
+        if (macros.isEmpty()) {
+          return;
         }
+
+        LOG.debug("Reporting unknown path macros " + macros + " in component " + componentName);
+        String format = "<p><i>%s</i> %s undefined. <a href=\"define\">Fix it</a></p>";
+        String productName = ApplicationNamesInfo.getInstance().getProductName();
+        String content = String.format(format, StringUtil.join(macros, ", "), macros.size() == 1 ? "is" : "are") +
+                         "<br>Path variables are used to substitute absolute paths " +
+                         "in " + productName + " project files " +
+                         "and allow project file sharing in version control systems.<br>" +
+                         "Some of the files describing the current project settings contain unknown path variables " +
+                         "and " + productName + " cannot restore those paths.";
+        new UnknownMacroNotification("Load Error", "Load error: undefined path variables", content, NotificationType.ERROR,
+                                     new NotificationListener() {
+                                       @Override
+                                       public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
+                                         checkUnknownMacros(store, project, true);
+                                       }
+                                     }, macros).notify(project);
       }
-    });
+    }, project.getDisposed());
+  }
+
+  public static void checkUnknownMacros(@NotNull IComponentStore store, @NotNull Project project, boolean showDialog) {
+    // default project doesn't have it
+    List<TrackingPathMacroSubstitutor> substitutors;
+    if (store instanceof IProjectStore) {
+      substitutors = ((IProjectStore)store).getSubstitutors();
+    }
+    else {
+      substitutors = Collections.emptyList();
+    }
+    Set<String> unknownMacros = new THashSet<String>();
+    for (TrackingPathMacroSubstitutor substitutor : substitutors) {
+      unknownMacros.addAll(substitutor.getUnknownMacros(null));
+    }
+
+    if (unknownMacros.isEmpty() || showDialog && !ProjectMacrosUtil.checkMacros(project, new THashSet<String>(unknownMacros))) {
+      return;
+    }
+
+    final PathMacros pathMacros = PathMacros.getInstance();
+    final Set<String> macrosToInvalidate = new THashSet<String>(unknownMacros);
+    for (Iterator<String> it = macrosToInvalidate.iterator(); it.hasNext(); ) {
+      String macro = it.next();
+      if (StringUtil.isEmptyOrSpaces(pathMacros.getValue(macro)) && !pathMacros.isIgnoredMacroName(macro)) {
+        it.remove();
+      }
+    }
+
+    if (macrosToInvalidate.isEmpty()) {
+      return;
+    }
+
+    Set<String> components = new THashSet<String>();
+    for (TrackingPathMacroSubstitutor substitutor : substitutors) {
+      components.addAll(substitutor.getComponents(macrosToInvalidate));
+    }
+
+    if (store.isReloadPossible(components)) {
+      for (TrackingPathMacroSubstitutor substitutor : substitutors) {
+        substitutor.invalidateUnknownMacros(macrosToInvalidate);
+      }
+
+      for (UnknownMacroNotification notification : NotificationsManager.getNotificationsManager().getNotificationsOfType(UnknownMacroNotification.class, project)) {
+        if (macrosToInvalidate.containsAll(notification.getMacros())) {
+          notification.expire();
+        }
+      }
+
+      store.reloadStates(components);
+    }
+    else if (Messages.showYesNoDialog(project, "Component could not be reloaded. Reload project?", "Configuration Changed", Messages.getQuestionIcon()) == Messages.YES) {
+      ProjectManagerEx.getInstanceEx().reloadProject(project);
+    }
   }
 
   @NotNull
index 48303b7336bffc3617db0b56b35d7da7dbe58a10..4fd37486ca51aa80ee866ce00308ac9e8ad5733a 100644 (file)
@@ -20,32 +20,23 @@ import com.intellij.diagnostic.PluginException;
 import com.intellij.ide.plugins.PluginManagerCore;
 import com.intellij.notification.Notification;
 import com.intellij.notification.NotificationType;
-import com.intellij.openapi.application.AccessToken;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.application.ApplicationNamesInfo;
-import com.intellij.openapi.application.WriteAction;
 import com.intellij.openapi.application.ex.ApplicationManagerEx;
 import com.intellij.openapi.components.PersistentStateComponent;
 import com.intellij.openapi.components.State;
-import com.intellij.openapi.components.StateStorage;
 import com.intellij.openapi.components.StateStorage.SaveSession;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.extensions.PluginId;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.project.ProjectBundle;
-import com.intellij.openapi.ui.Messages;
 import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.ShutDownTracker;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.SmartList;
-import com.intellij.util.containers.ContainerUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.Collection;
-import java.util.Set;
-
 public final class StoreUtil {
   private static final Logger LOG = Logger.getInstance(StoreUtil.class);
 
@@ -92,7 +83,11 @@ public final class StoreUtil {
 
   @NotNull
   public static <T> State getStateSpec(@NotNull PersistentStateComponent<T> persistentStateComponent) {
-    Class<? extends PersistentStateComponent> componentClass = persistentStateComponent.getClass();
+    return getStateSpecOrError(persistentStateComponent.getClass());
+  }
+
+  @NotNull
+  public static State getStateSpecOrError(@NotNull Class<? extends PersistentStateComponent> componentClass) {
     State spec = getStateSpec(componentClass);
     if (spec != null) {
       return spec;
@@ -118,90 +113,4 @@ public final class StoreUtil {
     while ((aClass = aClass.getSuperclass()) != null);
     return null;
   }
-
-  public enum ReloadComponentStoreStatus {
-    RESTART_AGREED,
-    RESTART_CANCELLED,
-    ERROR,
-    SUCCESS,
-  }
-
-  @NotNull
-  public static ReloadComponentStoreStatus reloadStore(@NotNull Set<StateStorage> changes, @NotNull IComponentStore store) {
-    Collection<String> notReloadableComponents;
-    boolean willBeReloaded = false;
-    try {
-      AccessToken token = WriteAction.start();
-      try {
-        notReloadableComponents = store.reload(changes);
-      }
-      catch (Throwable e) {
-        LOG.warn(e);
-        Messages.showWarningDialog(ProjectBundle.message("project.reload.failed", e.getMessage()),
-                                   ProjectBundle.message("project.reload.failed.title"));
-        return ReloadComponentStoreStatus.ERROR;
-      }
-      finally {
-        token.finish();
-      }
-
-      if (ContainerUtil.isEmpty(notReloadableComponents)) {
-        return ReloadComponentStoreStatus.SUCCESS;
-      }
-
-      willBeReloaded = askToRestart(store, notReloadableComponents, changes);
-      return willBeReloaded ? ReloadComponentStoreStatus.RESTART_AGREED : ReloadComponentStoreStatus.RESTART_CANCELLED;
-    }
-    finally {
-      if (!willBeReloaded) {
-        for (StateStorage storage : changes) {
-          if (storage instanceof StateStorageBase) {
-            ((StateStorageBase)storage).enableSaving();
-          }
-        }
-      }
-    }
-  }
-
-  // used in settings repository plugin
-  public static boolean askToRestart(@NotNull IComponentStore store,
-                                     @NotNull Collection<String> notReloadableComponents,
-                                     @Nullable Set<StateStorage> changedStorages) {
-    StringBuilder message = new StringBuilder();
-    String storeName = store instanceof IProjectStore ? "Project" : "Application";
-    message.append(storeName).append(' ');
-    message.append("components were changed externally and cannot be reloaded:\n\n");
-    int count = 0;
-    for (String component : notReloadableComponents) {
-      if (count == 10) {
-        message.append('\n').append("and ").append(notReloadableComponents.size() - count).append(" more").append('\n');
-      }
-      else {
-        message.append(component).append('\n');
-        count++;
-      }
-    }
-
-    message.append("\nWould you like to ");
-    if (store instanceof IProjectStore) {
-      message.append("reload project?");
-    }
-    else {
-      message.append(ApplicationManager.getApplication().isRestartCapable() ? "restart" : "shutdown").append(' ');
-      message.append(ApplicationNamesInfo.getInstance().getProductName()).append('?');
-    }
-
-    if (Messages.showYesNoDialog(message.toString(),
-                                 storeName + " Files Changed", Messages.getQuestionIcon()) == Messages.YES) {
-      if (changedStorages != null) {
-        for (StateStorage storage : changedStorages) {
-          if (storage instanceof StateStorageBase) {
-            ((StateStorageBase)storage).disableSaving();
-          }
-        }
-      }
-      return true;
-    }
-    return false;
-  }
 }
index 61c74f9d500df4955b065c3fe6cb1cec3fdc8ed2..45dbb43a5a4159f9ed8a60fcfe72da95ff3cae44 100644 (file)
@@ -15,6 +15,7 @@
  */
 package com.intellij.openapi.diff.impl.mergeTool;
 
+import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.diff.ActionButtonPresentation;
 import com.intellij.openapi.diff.DiffRequestFactory;
 import com.intellij.openapi.diff.MergeRequest;
@@ -27,6 +28,8 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public class DiffRequestFactoryImpl extends DiffRequestFactory {
+  private static final Logger LOG = Logger.getInstance(DiffRequestFactoryImpl.class);
+
   @Override
   public MergeRequest createMergeRequest(@NotNull String leftText,
                                          @NotNull String rightText,
@@ -42,6 +45,7 @@ public class DiffRequestFactoryImpl extends DiffRequestFactory {
                                   cancelButtonPresentation);
     }
     else {
+      LOG.warn("Document not found for " + file.getPresentableUrl() + "; FileType - " + file.getFileType().getName() + "; valid - " + file.isValid());
       return create3WayDiffRequest(leftText, rightText, originalContent, file.getFileType(), project, okButtonPresentation, cancelButtonPresentation);
     }
   }
index 5794c50843399549ed8275803877fee43dd577eb..2ba863334da8fe81887270ac52d34c0e1e8091bf 100644 (file)
@@ -183,15 +183,21 @@ public class ConfigurableExtensionPointUtil {
     String id = "configurable.group." + groupId;
     ResourceBundle bundle = getBundle(id + ".settings.display.name", configurables, alternative);
     if (bundle == null) {
+      bundle = OptionsBundle.getBundle();
       if ("root".equals(groupId)) {
-        LOG.error("OptionsBundle does not contain root group");
+        try {
+          String value = bundle.getString("configurable.group.root.settings.display.name");
+          LOG.error("OptionsBundle does not contain root group", value);
+        }
+        catch (Exception exception) {
+          LOG.error("OptionsBundle does not contain root group", exception);
+        }
       }
       else {
         LOG.warn("use other group instead of unexpected one: " + groupId);
         groupId = "other";
         id = "configurable.group." + groupId;
       }
-      bundle = OptionsBundle.getBundle();
     }
     Node<SortedConfigurableGroup> node = Node.get(tree, groupId);
     if (node.myValue == null) {
index 72b3d2c754fa37e11433a1bfec1a83155b58ac64..90facde4098044a675b6de8f42194864e2966995 100644 (file)
@@ -31,7 +31,5 @@ public interface ProjectEx extends Project {
 
   void setOptimiseTestLoadSpeed(boolean optimiseTestLoadSpeed);
 
-  void checkUnknownMacros(final boolean showDialog);
-
   void setProjectName(@NotNull String name);
 }
index 3225242ccac88955f0a42e620999934cb5499ef5..e8ebea95c75b9d4991188124aa4a5386ed433d2e 100644 (file)
@@ -18,9 +18,7 @@ package com.intellij.openapi.project.impl;
 import com.intellij.ide.RecentProjectsManager;
 import com.intellij.ide.plugins.IdeaPluginDescriptor;
 import com.intellij.ide.startup.StartupManagerEx;
-import com.intellij.notification.NotificationsManager;
 import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.application.PathMacros;
 import com.intellij.openapi.application.ex.ApplicationEx;
 import com.intellij.openapi.application.ex.ApplicationManagerEx;
 import com.intellij.openapi.application.impl.ApplicationInfoImpl;
@@ -30,7 +28,6 @@ import com.intellij.openapi.components.impl.ProjectPathMacroManager;
 import com.intellij.openapi.components.impl.stores.IComponentStore;
 import com.intellij.openapi.components.impl.stores.IProjectStore;
 import com.intellij.openapi.components.impl.stores.StoreUtil;
-import com.intellij.openapi.components.impl.stores.UnknownMacroNotification;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.extensions.ExtensionPointName;
 import com.intellij.openapi.extensions.Extensions;
@@ -43,17 +40,15 @@ import com.intellij.openapi.project.ProjectManagerAdapter;
 import com.intellij.openapi.project.ex.ProjectEx;
 import com.intellij.openapi.project.ex.ProjectManagerEx;
 import com.intellij.openapi.startup.StartupManager;
-import com.intellij.openapi.ui.Messages;
 import com.intellij.openapi.util.Key;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.util.io.FileUtilRt;
-import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.wm.WindowManager;
 import com.intellij.openapi.wm.impl.FrameTitleBuilder;
+import com.intellij.psi.impl.DebugUtil;
 import com.intellij.util.TimedReference;
 import com.intellij.util.pico.ConstructorInjectionComponentAdapter;
-import gnu.trove.THashSet;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -62,17 +57,15 @@ import org.picocontainer.*;
 
 import javax.swing.*;
 import java.io.File;
-import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 public class ProjectImpl extends PlatformComponentManagerImpl implements ProjectEx {
   private static final Logger LOG = Logger.getInstance("#com.intellij.project.impl.ProjectImpl");
 
   public static final String NAME_FILE = ".name";
-  public static Key<Long> CREATION_TIME = Key.create("ProjectImpl.CREATION_TIME");
+  public static final Key<Long> CREATION_TIME = Key.create("ProjectImpl.CREATION_TIME");
+  public static final Key<String> CREATION_TRACE = Key.create("ProjectImpl.CREATION_TRACE");
 
   private ProjectManager myProjectManager;
   private MyProjectManagerListener myProjectManagerListener;
@@ -89,6 +82,9 @@ public class ProjectImpl extends PlatformComponentManagerImpl implements Project
     super(ApplicationManager.getApplication(), "Project " + (projectName == null ? filePath : projectName));
 
     putUserData(CREATION_TIME, System.nanoTime());
+    if (ApplicationManager.getApplication().isUnitTestMode()) {
+      putUserData(CREATION_TRACE, DebugUtil.currentStackTrace());
+    }
 
     getPicoContainer().registerComponentInstance(Project.class, this);
 
@@ -372,7 +368,8 @@ public class ProjectImpl extends PlatformComponentManagerImpl implements Project
     // can call dispose only via com.intellij.ide.impl.ProjectUtil.closeAndDispose()
     LOG.assertTrue(application.isUnitTestMode() || !ProjectManagerEx.getInstanceEx().isProjectOpened(this));
 
-    LOG.assertTrue(!isDisposed());
+    // we use super here, because temporarilyDisposed will be true if project closed
+    LOG.assertTrue(!super.isDisposed());
     if (myProjectManagerListener != null) {
       myProjectManager.removeProjectManagerListener(this, myProjectManagerListener);
     }
@@ -448,68 +445,6 @@ public class ProjectImpl extends PlatformComponentManagerImpl implements Project
     return false;
   }
 
-  @Override
-  public void checkUnknownMacros(final boolean showDialog) {
-    final IComponentStore stateStore = ComponentsPackage.getStateStore(this);
-    // default project doesn't have it
-    List<TrackingPathMacroSubstitutor> substitutors;
-    if (stateStore instanceof IProjectStore) {
-      substitutors = ((IProjectStore)stateStore).getSubstitutors();
-    }
-    else {
-      substitutors = Collections.emptyList();
-    }
-    Set<String> unknownMacros = new THashSet<String>();
-    for (TrackingPathMacroSubstitutor substitutor : substitutors) {
-      unknownMacros.addAll(substitutor.getUnknownMacros(null));
-    }
-
-    if (unknownMacros.isEmpty() || showDialog && !ProjectMacrosUtil.checkMacros(this, new THashSet<String>(unknownMacros))) {
-      return;
-    }
-
-    final PathMacros pathMacros = PathMacros.getInstance();
-    final Set<String> macrosToInvalidate = new THashSet<String>(unknownMacros);
-    for (Iterator<String> it = macrosToInvalidate.iterator(); it.hasNext(); ) {
-      String macro = it.next();
-      if (StringUtil.isEmptyOrSpaces(pathMacros.getValue(macro)) && !pathMacros.isIgnoredMacroName(macro)) {
-        it.remove();
-      }
-    }
-
-    if (!macrosToInvalidate.isEmpty()) {
-      final Set<String> components = new THashSet<String>();
-      for (TrackingPathMacroSubstitutor substitutor : substitutors) {
-        components.addAll(substitutor.getComponents(macrosToInvalidate));
-      }
-
-      if (stateStore.isReloadPossible(components)) {
-        for (TrackingPathMacroSubstitutor substitutor : substitutors) {
-          substitutor.invalidateUnknownMacros(macrosToInvalidate);
-        }
-
-        for (UnknownMacroNotification notification : NotificationsManager.getNotificationsManager().getNotificationsOfType(UnknownMacroNotification.class, this)) {
-          if (macrosToInvalidate.containsAll(notification.getMacros())) {
-            notification.expire();
-          }
-        }
-
-        ApplicationManager.getApplication().runWriteAction(new Runnable() {
-          @Override
-          public void run() {
-            stateStore.reinitComponents(components, true);
-          }
-        });
-      }
-      else {
-        if (Messages.showYesNoDialog(this, "Component could not be reloaded. Reload project?", "Configuration Changed",
-                                     Messages.getQuestionIcon()) == Messages.YES) {
-          ProjectManagerEx.getInstanceEx().reloadProject(this);
-        }
-      }
-    }
-  }
-
   @NonNls
   @Override
   public String toString() {
index c9a3c0a7aea0a2502cf547210156a50777035825..5b7dba1f12f699be2ef5eb3600f9ee80ce600d79 100644 (file)
@@ -28,19 +28,12 @@ import com.intellij.notification.NotificationListener;
 import com.intellij.notification.NotificationType;
 import com.intellij.notification.NotificationsManager;
 import com.intellij.openapi.Disposable;
-import com.intellij.openapi.application.Application;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.application.ApplicationNamesInfo;
 import com.intellij.openapi.application.ModalityState;
-import com.intellij.openapi.application.ex.ApplicationManagerEx;
-import com.intellij.openapi.components.ComponentManager;
-import com.intellij.openapi.components.ComponentsPackage;
-import com.intellij.openapi.components.StateStorage;
-import com.intellij.openapi.components.impl.stores.*;
-import com.intellij.openapi.components.impl.stores.StoreUtil.ReloadComponentStoreStatus;
+import com.intellij.openapi.components.impl.stores.StorageUtil;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
-import com.intellij.openapi.module.Module;
 import com.intellij.openapi.progress.*;
 import com.intellij.openapi.progress.util.ProgressWindow;
 import com.intellij.openapi.project.*;
@@ -51,19 +44,12 @@ import com.intellij.openapi.util.*;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.openapi.vfs.VirtualFileManager;
-import com.intellij.openapi.vfs.ex.VirtualFileManagerAdapter;
 import com.intellij.openapi.vfs.impl.local.FileWatcher;
 import com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl;
-import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
-import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent;
 import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeFrame;
 import com.intellij.util.ArrayUtil;
-import com.intellij.util.SingleAlarm;
-import com.intellij.util.SmartList;
 import com.intellij.util.TimeoutUtil;
 import com.intellij.util.containers.ContainerUtil;
-import com.intellij.util.messages.MessageBus;
 import com.intellij.util.ui.UIUtil;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
@@ -74,13 +60,11 @@ import javax.swing.event.HyperlinkEvent;
 import java.io.File;
 import java.io.IOException;
 import java.util.*;
-import java.util.concurrent.atomic.AtomicInteger;
 
 public class ProjectManagerImpl extends ProjectManagerEx implements Disposable {
   private static final Logger LOG = Logger.getInstance(ProjectManagerImpl.class);
 
   private static final Key<List<ProjectManagerListener>> LISTENERS_IN_PROJECT_KEY = Key.create("LISTENERS_IN_PROJECT_KEY");
-  private static final Key<Set<StateStorage>> CHANGED_FILES_KEY = Key.create("CHANGED_FILES_KEY");
 
   @SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized")
   private ProjectImpl myDefaultProject; // Only used asynchronously in save and dispose, which itself are synchronized.
@@ -89,22 +73,9 @@ public class ProjectManagerImpl extends ProjectManagerEx implements Disposable {
   private final Object lock = new Object();
   private final List<ProjectManagerListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
 
-  private final SingleAlarm myChangedFilesAlarm;
-  private final Set<StateStorage> myChangedApplicationFiles = new LinkedHashSet<StateStorage>();
-  private final AtomicInteger myReloadBlockCount = new AtomicInteger(0);
-
   private final ProgressManager myProgressManager;
   private volatile boolean myDefaultProjectWasDisposed;
 
-  private final Runnable restartApplicationOrReloadProjectTask = new Runnable() {
-    @Override
-    public void run() {
-      if (isReloadUnblocked() && tryToReloadApplication()) {
-        askToReloadProjectIfConfigFilesChangedExternally();
-      }
-    }
-  };
-
   @NotNull
   private static List<ProjectManagerListener> getListeners(@NotNull Project project) {
     List<ProjectManagerListener> array = project.getUserData(LISTENERS_IN_PROJECT_KEY);
@@ -112,38 +83,9 @@ public class ProjectManagerImpl extends ProjectManagerEx implements Disposable {
     return array;
   }
 
-  public ProjectManagerImpl(@NotNull VirtualFileManager virtualFileManager, ProgressManager progressManager) {
+  public ProjectManagerImpl(ProgressManager progressManager) {
     myProgressManager = progressManager;
-    Application app = ApplicationManager.getApplication();
-    MessageBus messageBus = app.getMessageBus();
-
-    messageBus.connect().subscribe(StateStorageManager.STORAGE_TOPIC, new StorageManagerListener() {
-      @Override
-      public void storageFileChanged(@NotNull VFileEvent event, @NotNull StateStorage storage, @NotNull ComponentManager componentManager) {
-        if (event instanceof VFilePropertyChangeEvent) {
-          // ignore because doesn't affect content
-          return;
-        }
-
-        if (event.getRequestor() instanceof StateStorage.SaveSession ||
-            event.getRequestor() instanceof StateStorage ||
-            event.getRequestor() instanceof ProjectManagerImpl) {
-          return;
-        }
-
-        Project project;
-        if (componentManager instanceof Project) {
-          project = (Project)componentManager;
-        }
-        else {
-          project = componentManager instanceof Module ? ((Module)componentManager).getProject() : null;
-        }
-
-        registerProjectToReload(project, storage);
-      }
-    });
-
-    final ProjectManagerListener busPublisher = messageBus.syncPublisher(TOPIC);
+    final ProjectManagerListener busPublisher = ApplicationManager.getApplication().getMessageBus().syncPublisher(TOPIC);
     addProjectManagerListener(
       new ProjectManagerListener() {
         @Override
@@ -181,25 +123,11 @@ public class ProjectManagerImpl extends ProjectManagerEx implements Disposable {
         }
       }
     );
-
-    virtualFileManager.addVirtualFileManagerListener(new VirtualFileManagerAdapter() {
-      @Override
-      public void beforeRefreshStart(boolean asynchronous) {
-        blockReloadingProjectOnExternalChanges();
-      }
-
-      @Override
-      public void afterRefreshFinish(boolean asynchronous) {
-        unblockReloadingProjectOnExternalChanges();
-      }
-    });
-    myChangedFilesAlarm = new SingleAlarm(restartApplicationOrReloadProjectTask, 300);
   }
 
   @Override
   public void dispose() {
     ApplicationManager.getApplication().assertWriteAccessAllowed();
-    Disposer.dispose(myChangedFilesAlarm);
     if (myDefaultProject != null) {
       Disposer.dispose(myDefaultProject);
 
@@ -608,71 +536,6 @@ public class ProjectManagerImpl extends ProjectManagerEx implements Disposable {
     WelcomeFrame.showIfNoProjectOpened();
   }
 
-  private void askToReloadProjectIfConfigFilesChangedExternally() {
-    List<Project> projectsToReload = new SmartList<Project>();
-    for (Project project : getOpenProjects()) {
-      if (project.isDisposed()) {
-        continue;
-      }
-
-      Set<StateStorage> changes = CHANGED_FILES_KEY.get(project);
-      if (changes == null) {
-        continue;
-      }
-
-      CHANGED_FILES_KEY.set(project, null);
-      if (!changes.isEmpty() && StoreUtil.reloadStore(changes, ComponentsPackage.getStateStore(project)) == ReloadComponentStoreStatus.RESTART_AGREED) {
-        projectsToReload.add(project);
-      }
-    }
-
-    for (Project project : projectsToReload) {
-      doReloadProject(project);
-    }
-  }
-
-  private boolean tryToReloadApplication() {
-    if (ApplicationManager.getApplication().isDisposed()) {
-      return false;
-    }
-    if (myChangedApplicationFiles.isEmpty()) {
-      return true;
-    }
-
-    Set<StateStorage> changes = new LinkedHashSet<StateStorage>(myChangedApplicationFiles);
-    myChangedApplicationFiles.clear();
-
-    ReloadComponentStoreStatus status = StoreUtil.reloadStore(changes, ComponentsPackage.getStateStore(ApplicationManager.getApplication()));
-    if (status == ReloadComponentStoreStatus.RESTART_AGREED) {
-      ApplicationManagerEx.getApplicationEx().restart(true);
-      return false;
-    }
-    else {
-      return status == ReloadComponentStoreStatus.SUCCESS || status == ReloadComponentStoreStatus.RESTART_CANCELLED;
-    }
-  }
-
-  @Override
-  public void blockReloadingProjectOnExternalChanges() {
-    myReloadBlockCount.incrementAndGet();
-  }
-
-  @Override
-  public void unblockReloadingProjectOnExternalChanges() {
-    assert myReloadBlockCount.get() > 0;
-    if (myReloadBlockCount.decrementAndGet() == 0 && myChangedFilesAlarm.isEmpty()) {
-      ApplicationManager.getApplication().invokeLater(restartApplicationOrReloadProjectTask, ModalityState.NON_MODAL, ApplicationManager.getApplication().getDisposed());
-    }
-  }
-
-  private boolean isReloadUnblocked() {
-    int count = myReloadBlockCount.get();
-    if (LOG.isDebugEnabled()) {
-      LOG.debug("[RELOAD] myReloadBlockCount = " + count);
-    }
-    return count == 0;
-  }
-
   @Override
   @TestOnly
   public void openTestProject(@NotNull final Project project) {
@@ -690,56 +553,12 @@ public class ProjectManagerImpl extends ProjectManagerEx implements Disposable {
     return Arrays.asList(getOpenProjects());
   }
 
-  @Override
-  public void saveChangedProjectFile(@NotNull VirtualFile file, @NotNull Project project) {
-    StateStorageManager storageManager = ComponentsPackage.getStateStore(project).getStateStorageManager();
-    String fileSpec = storageManager.collapseMacros(file.getPath());
-    Couple<Collection<FileStorage>> storages = storageManager.getCachedFileStateStorages(Collections.singletonList(fileSpec), Collections.<String>emptyList());
-    FileStorage storage = ContainerUtil.getFirstItem(storages.first);
-    // if empty, so, storage is not yet loaded, so, we don't have to reload
-    if (storage != null) {
-      registerProjectToReload(project, storage);
-    }
-  }
-
-  private void registerProjectToReload(@Nullable Project project, @NotNull StateStorage storage) {
-    if (LOG.isDebugEnabled()) {
-      LOG.debug("[RELOAD] Registering project to reload: " + storage, new Exception());
-    }
-
-    Set<StateStorage> changes;
-    if (project == null) {
-      changes = myChangedApplicationFiles;
-    }
-    else {
-      changes = CHANGED_FILES_KEY.get(project);
-      if (changes == null) {
-        changes = new LinkedHashSet<StateStorage>();
-        CHANGED_FILES_KEY.set(project, changes);
-      }
-    }
-
-    //noinspection SynchronizationOnLocalVariableOrMethodParameter
-    synchronized (changes) {
-      changes.add(storage);
-    }
-
-    if (storage instanceof StateStorageBase) {
-      ((StateStorageBase)storage).disableSaving();
-    }
-
-    if (isReloadUnblocked()) {
-      myChangedFilesAlarm.cancelAndRequest();
-    }
-  }
-
   @Override
   public void reloadProject(@NotNull Project project) {
-    CHANGED_FILES_KEY.set(project, null);
     doReloadProject(project);
   }
 
-  private static void doReloadProject(@NotNull Project project) {
+  protected static void doReloadProject(@NotNull Project project) {
     final Ref<Project> projectRef = Ref.create(project);
     ProjectReloadState.getInstance(project).onBeforeAutomaticProjectReload();
     ApplicationManager.getApplication().invokeLater(new Runnable() {
@@ -986,4 +805,16 @@ public class ProjectManagerImpl extends ProjectManagerEx implements Disposable {
       super.expire();
     }
   }
+
+  @Override
+  public void saveChangedProjectFile(@NotNull VirtualFile file, @NotNull Project project) {
+  }
+
+  @Override
+  public void blockReloadingProjectOnExternalChanges() {
+  }
+
+  @Override
+  public void unblockReloadingProjectOnExternalChanges() {
+  }
 }
index 0f0d73d60f9f24c2a324af8782dd52fff11d6c44..65eec3e657b20c2a3e7b2c5f0aace2b4339eac60 100644 (file)
@@ -492,7 +492,7 @@ macro.prompt.preview=<params>
 macro.fileprompt.preview=<filename>
 macro.sourcepath.entry=Entry in the sourcepath the element belongs to
 macro.project.sourcepath=Project's sourcepath
-error.plugins.should.not.have.cyclic.dependencies=Plugins should not have cyclic dependencies:\n
+error.plugins.should.not.have.cyclic.dependencies=Plugins should not have cyclic dependencies:
 error.plugin.was.not.installed=Plugin {0} was not installed: {1}
 title.failed.to.download=Failed to Download
 error.plugins.were.not.loaded=Error loading plugins:\n{0}\nPlugins were not loaded.\nCorrect the above error and restart IDEA.
@@ -501,7 +501,7 @@ message.duplicate.plugin.id=Duplicate plugin id:
 error.required.plugin.not.installed=Plugin "{0}" was not loaded: required plugin "{1}" not installed.
 error.required.plugin.disabled=Plugin "{0}" was not loaded: required plugin "{1}" is disabled.
 error.plugins.without.id.found=There were plugins without id found, all such plugins were skipped.
-error.problems.found.loading.plugins=Problems found loading plugins:\n
+error.problems.found.loading.plugins=Problems found loading plugins:<p/>
 column.plugins.name=Name
 column.plugins.date=Date
 column.plugins.downloads=Downloads
index 6a474421fd8bf6236ce3efb2ed90f1d73e9265b4..c58019c209078c57ae552b7f8dc42e9af4612670 100644 (file)
@@ -2,7 +2,7 @@
   <application-components>
     <component>
       <interface-class>com.intellij.openapi.project.ProjectManager</interface-class>
-      <implementation-class>com.intellij.openapi.project.impl.ProjectManagerImpl</implementation-class>
+      <implementation-class>com.intellij.configurationStore.StoreAwareProjectManager</implementation-class>
     </component>
 
     <component>
index cc40b08cf5c9d6e02f12db6695652e02077793df..cc670d1529f96816da064e0d4c5b3033276da281 100644 (file)
@@ -15,8 +15,6 @@
  */
 package com.intellij.openapi.components;
 
-import com.intellij.openapi.vfs.VirtualFileEvent;
-import com.intellij.util.messages.Topic;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -24,29 +22,15 @@ import java.io.IOException;
 import java.util.Set;
 
 public interface StateStorage {
-  /**
-   * @deprecated use StateStorageManager.STORAGE_TOPIC (to be removed in IDEA 16)
-   * app storage files changed
-   */
-  @SuppressWarnings("unused")
-  @Deprecated
-  Topic<Listener> STORAGE_TOPIC = new Topic<Listener>("STORAGE_LISTENER", Listener.class, Topic.BroadcastDirection.NONE);
-
-  /**
-   * @deprecated use StateStorageManager.STORAGE_TOPIC (to be removed in IDEA 16)
-   * project storage files changes (project or modules)
-   */
-  @SuppressWarnings("unused")
-  @Deprecated
-  Topic<Listener> PROJECT_STORAGE_TOPIC = new Topic<Listener>("PROJECT_STORAGE_LISTENER", Listener.class, Topic.BroadcastDirection.NONE);
-
   /**
    * You can call this method only once.
    * If state exists and not archived - not-null result.
    * If doesn't exists or archived - null result.
    */
   @Nullable
-  <T> T getState(@Nullable Object component, @NotNull String componentName, @NotNull Class<T> stateClass, @Nullable T mergeInto);
+  <T> T getState(@Nullable Object component, @NotNull String componentName, @NotNull Class<T> stateClass, @Nullable T mergeInto, boolean reload);
+
+  <T> T getState(@Nullable Object component, @NotNull String componentName, @NotNull Class<T> stateClass);
 
   boolean hasState(@NotNull String componentName, boolean reloadData);
 
@@ -71,8 +55,4 @@ public interface StateStorage {
   interface SaveSession {
     void save() throws IOException;
   }
-
-  interface Listener {
-    void storageFileChanged(@NotNull VirtualFileEvent event, @NotNull StateStorage storage);
-  }
-}
+}
\ No newline at end of file
index 234b8350564311f9e08c2871d34f41b9e43ce5f2..e67df90b5462e0df3d4069d0cefbaa7cc30d034c 100644 (file)
@@ -29,10 +29,6 @@ public class MockProjectEx  extends MockProject implements ProjectEx {
   public void setProjectName(@NotNull String name) {
   }
 
-  @Override
-  public void checkUnknownMacros(final boolean showDialog) {
-  }
-
   @Override
   public void init() {
   }
@@ -46,4 +42,4 @@ public class MockProjectEx  extends MockProject implements ProjectEx {
   public void setOptimiseTestLoadSpeed(final boolean optimiseTestLoadSpeed) {
     throw new UnsupportedOperationException("Method setOptimiseTestLoadSpeed not implemented in " + getClass());
   }
-}
+}
\ No newline at end of file
index a588643d5fdf85943a4748519b02b7017983dcea..1eebed7a792f6b3e9265719473c2dd40e3414aaa 100644 (file)
@@ -26,9 +26,10 @@ import com.intellij.openapi.project.ex.ProjectManagerEx
 import com.intellij.openapi.project.impl.ProjectManagerImpl
 import com.intellij.openapi.util.Disposer
 import com.intellij.openapi.util.io.FileUtil
-import com.intellij.openapi.util.io.FileUtilRt
+import com.intellij.openapi.vfs.impl.VirtualFilePointerManagerImpl
 import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS
 import com.intellij.openapi.vfs.newvfs.persistent.PersistentFSImpl
+import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager
 import com.intellij.testFramework.fixtures.IdeaProjectTestFixture
 import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory
 import com.intellij.testFramework.fixtures.TestFixtureBuilder
@@ -41,6 +42,10 @@ import org.junit.runners.model.Statement
 import java.io.ByteArrayOutputStream
 import java.io.File
 import java.io.PrintStream
+import java.lang.annotation.ElementType
+import java.lang.annotation.Retention
+import java.lang.annotation.RetentionPolicy
+import java.lang.annotation.Target
 import java.lang.reflect.InvocationTargetException
 import java.util.concurrent.atomic.AtomicBoolean
 import javax.swing.SwingUtilities
@@ -61,7 +66,7 @@ public class ProjectRule() : ExternalResource() {
     private fun createLightProject(): ProjectEx {
       (PersistentFS.getInstance() as PersistentFSImpl).cleanPersistedContents()
 
-      val projectFile = File("${FileUtilRt.generateRandomTemporaryPath().path}${ProjectFileType.DOT_DEFAULT_EXTENSION}")
+      val projectFile = File("${generateTemporaryPath("light_temp_shared_project${ProjectFileType.DOT_DEFAULT_EXTENSION}").path}")
 
       val buffer = ByteArrayOutputStream()
       java.lang.Throwable(projectFile.path).printStackTrace(PrintStream(buffer))
@@ -75,6 +80,8 @@ public class ProjectRule() : ExternalResource() {
           FileUtil.delete(projectFile)
         }
       })
+
+      (VirtualFilePointerManager.getInstance() as VirtualFilePointerManagerImpl).storePointers()
       return project
     }
 
@@ -199,4 +206,23 @@ public fun runInEdtAndWait(runnable: () -> Unit) {
       throw e.getCause() ?: e
     }
   }
+}
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD, ElementType.TYPE)
+annotation public class RunsInEdt
+
+public class EdtRule : TestRule {
+  override fun apply(base: Statement, description: Description): Statement {
+    return if ((description.getAnnotation(javaClass<RunsInEdt>()) ?: description.getTestClass()!!.getAnnotation(javaClass<RunsInEdt>())) == null) {
+      base
+    }
+    else {
+      object : Statement() {
+        override fun evaluate() {
+          runInEdtAndWait { base.evaluate() }
+        }
+      }
+    }
+  }
 }
\ No newline at end of file
index 4fff1573ee23505b7f9e119aad71d3155314286c..54d6a8656513a1c2c388307536247c60351159e6 100644 (file)
@@ -48,47 +48,52 @@ public class TemporaryDirectory : ExternalResource() {
    * Directory is not created.
    */
   public fun newDirectory(directoryName: String? = null): File {
-    val file = generateRandomTemporaryPath(directoryName)
+    val file = generatePath(directoryName)
     val fs = LocalFileSystem.getInstance()
     if (fs != null) {
       // If a temp directory is reused from some previous test run, there might be cached children in its VFS. Ensure they're removed.
       val virtualFile = fs.findFileByIoFile(file)
       if (virtualFile != null) {
-        VfsUtil.markDirtyAndRefresh(false, false, false, virtualFile)
+        VfsUtil.markDirtyAndRefresh(false, true, true, virtualFile)
       }
     }
     return file
   }
 
-  private fun generateRandomTemporaryPath(directoryName: String?): File {
-    val tempDirectory = FileUtilRt.getTempDirectory()
-    var testFileName = sanitizedName!!
-    if (directoryName != null) {
-      testFileName += "_$directoryName"
+  public fun generatePath(suffix: String?): File {
+    var fileName = sanitizedName!!
+    if (suffix != null) {
+      fileName += "_$suffix"
     }
 
-    var file = File(tempDirectory, testFileName)
-    var i = 0
-    while (file.exists() && i < 9) {
-      file = File(tempDirectory, "${testFileName}_$i")
-      i++
-    }
-
-    if (file.exists()) {
-      throw IOException("Cannot generate unique random path")
-    }
+    var file = generateTemporaryPath(fileName)
     files.add(file)
     return file
   }
 
   public fun newVirtualDirectory(directoryName: String? = null): VirtualFile {
-    val file = generateRandomTemporaryPath(directoryName)
+    val file = generatePath(directoryName)
     if (!file.mkdirs()) {
       throw AssertionError("Cannot create directory")
     }
 
     val virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file)
-    VfsUtil.markDirtyAndRefresh(false, false, false, virtualFile)
+    VfsUtil.markDirtyAndRefresh(false, true, true, virtualFile)
     return virtualFile!!
   }
 }
+
+public fun generateTemporaryPath(fileName: String?): File {
+  val tempDirectory = FileUtilRt.getTempDirectory()
+  var file = File(tempDirectory, fileName)
+  var i = 0
+  while (file.exists() && i < 9) {
+    file = File(tempDirectory, "${fileName}_$i")
+    i++
+  }
+
+  if (file.exists()) {
+    throw IOException("Cannot generate unique random path")
+  }
+  return file
+}
\ No newline at end of file
index 02ac232756928364f07877e84f4f5e78dea961e0..35f7f343dc9cdaedf08ecb335553f4adfd5eb34c 100644 (file)
 package git4idea.config;
 
 import com.intellij.execution.ExecutableValidator;
-import com.intellij.execution.configurations.GeneralCommandLine;
-import com.intellij.execution.process.CapturingProcessHandler;
-import com.intellij.execution.process.ProcessOutput;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vcs.VcsException;
-import com.intellij.openapi.vfs.CharsetToolkit;
 import git4idea.i18n.GitBundle;
 import org.jetbrains.annotations.NotNull;
 
+import java.util.Collections;
+
 /**
  * Project service that is used to check whether currently set git executable is valid (just calls 'git version' and parses the output),
  * and to display notification to the user proposing to fix the project set up.
@@ -49,16 +47,7 @@ public class GitExecutableValidator extends ExecutableValidator {
 
   @Override
   public boolean isExecutableValid(@NotNull String executable) {
-    try {
-      GeneralCommandLine commandLine = new GeneralCommandLine();
-      commandLine.setExePath(executable);
-      commandLine.addParameter("--version");
-      CapturingProcessHandler handler = new CapturingProcessHandler(commandLine.createProcess(), CharsetToolkit.getDefaultSystemCharset());
-      ProcessOutput result = handler.runProcess(30 * 1000);
-      return !result.isTimeout() && (result.getExitCode() == 0) && result.getStderr().isEmpty();
-    } catch (Throwable e) {
-      return false;
-    }
+    return doCheckExecutable(executable, Collections.singletonList("--version"));
   }
 
   /**
index cb3be1a23ca28e91abc21d856977fc95643b1209..abf4235c3441d07fb37018fbd0c76f9ebf87e962 100644 (file)
@@ -16,6 +16,7 @@
 package org.jetbrains.idea.maven.server.embedder;
 
 import org.apache.maven.artifact.manager.WagonManager;
+import org.apache.maven.artifact.repository.ArtifactRepository;
 import org.apache.maven.artifact.repository.DefaultArtifactRepository;
 import org.apache.maven.wagon.ConnectionException;
 import org.apache.maven.wagon.ResourceDoesNotExistException;
@@ -51,9 +52,9 @@ public class Maven2ServerIndexFetcher implements ResourceFetcher {
   }
 
   public void connect(String _ignoredContextId, String _ignoredUrl) throws IOException {
-    String mirrorUrl = myWagonManager.getMirrorRepository(new DefaultArtifactRepository(myOriginalRepositoryId,
-                                                                                        myOriginalRepositoryUrl,
-                                                                                        null)).getUrl();
+    final ArtifactRepository mirrorRepository = myWagonManager.getMirrorRepository(
+      new DefaultArtifactRepository(myOriginalRepositoryId, myOriginalRepositoryUrl, null));
+    String mirrorUrl = mirrorRepository.getUrl();
     String indexUrl = mirrorUrl + (mirrorUrl.endsWith("/") ? "" : "/") + ".index";
     Repository repository = new Repository(myOriginalRepositoryId, indexUrl);
 
@@ -62,8 +63,8 @@ public class Maven2ServerIndexFetcher implements ResourceFetcher {
       myWagon.addTransferListener(myListener);
 
       myWagon.connect(repository,
-                      myWagonManager.getAuthenticationInfo(repository.getId()),
-                      myWagonManager.getProxy(repository.getProtocol()));
+                      myWagonManager.getAuthenticationInfo(mirrorRepository.getId()),
+                      myWagonManager.getProxy(mirrorRepository.getProtocol()));
     }
     catch (AuthenticationException e) {
       IOException newEx = new IOException("Authentication exception connecting to " + repository);
index 7a17a7ee105b3f141370948d4f70283c3072765a..07118764e417bb000b2a2536056a44d888f1913d 100644 (file)
@@ -58,7 +58,8 @@ public class Maven3ServerIndexFetcher extends AbstractResourceFetcher {
     ArtifactRepository artifactRepository =
       myRepositorySystem.createArtifactRepository(myOriginalRepositoryId, myOriginalRepositoryUrl, null, null, null);
 
-    String mirrorUrl = myWagonManager.getMirrorRepository(artifactRepository).getUrl();
+    final ArtifactRepository mirrorRepository = myWagonManager.getMirrorRepository(artifactRepository);
+    String mirrorUrl = mirrorRepository.getUrl();
     String indexUrl = mirrorUrl + (mirrorUrl.endsWith("/") ? "" : "/") + ".index";
     Repository repository = new Repository(myOriginalRepositoryId, indexUrl);
 
@@ -67,8 +68,8 @@ public class Maven3ServerIndexFetcher extends AbstractResourceFetcher {
       myWagon.addTransferListener(myListener);
 
       myWagon.connect(repository,
-                      myWagonManager.getAuthenticationInfo(repository.getId()),
-                      myWagonManager.getProxy(repository.getProtocol()));
+                      myWagonManager.getAuthenticationInfo(mirrorRepository.getId()),
+                      myWagonManager.getProxy(mirrorRepository.getProtocol()));
     }
     catch (AuthenticationException e) {
       IOException newEx = new IOException("Authentication exception connecting to " + repository);
index 04a3074f9997350df3a778401517061e6ceb1246..320233266411b970c69c03cf8e695c78b28354a2 100644 (file)
@@ -128,7 +128,7 @@ class IcsManager(dir: File) {
 
   private fun registerProjectLevelProviders(project: Project) {
     val storageManager = project.stateStore.getStateStorageManager()
-    val projectId = storageManager.getStateStorage(StoragePathMacros.WORKSPACE_FILE, RoamingType.DISABLED).getState(ProjectId(), "IcsProjectId", javaClass<ProjectId>(), null)
+    val projectId = storageManager.getStateStorage(StoragePathMacros.WORKSPACE_FILE, RoamingType.DISABLED).getState(ProjectId(), "IcsProjectId", javaClass<ProjectId>())
     if (projectId == null || projectId.uid == null) {
       // not mapped, if user wants, he can map explicitly, we don't suggest
       // we cannot suggest "map to ICS" for any project that user opens, it will be annoying
index 39603e94ba6d001090dc92ac8574a3233b2d39d9..44e3730b946d53e67665f914f1120b6384b160b5 100644 (file)
@@ -15,6 +15,7 @@
  */
 package org.jetbrains.settingsRepository
 
+import com.intellij.configurationStore.ComponentStoreImpl
 import com.intellij.notification.Notification
 import com.intellij.notification.Notifications
 import com.intellij.notification.NotificationsAdapter
@@ -144,7 +145,7 @@ class AutoSyncManager(private val icsManager: IcsManager) {
         app.invokeAndWait({
           catchAndLog {
             val updateResult = updater.merge()
-            if (!onAppExit && !app.isDisposeInProgress() && updateResult != null && updateStoragesFromStreamProvider(app.stateStore, updateResult)) {
+            if (!onAppExit && !app.isDisposeInProgress() && updateResult != null && updateStoragesFromStreamProvider(app.stateStore as ComponentStoreImpl, updateResult)) {
               // force to avoid saveAll & confirmation
               app.exit(true, true, true, true)
             }
index da19558418771a13ef4b0c3fc97882d7a916cb3a..b1e70db4d037d93e41362262f2946777b61593bd 100644 (file)
  */
 package org.jetbrains.settingsRepository
 
-import com.intellij.configurationStore.ComponentStoreImpl
-import com.intellij.configurationStore.SchemeManagerFactoryBase
-import com.intellij.configurationStore.XmlElementStorage
+import com.intellij.configurationStore.*
 import com.intellij.openapi.application.ApplicationManager
 import com.intellij.openapi.application.WriteAction
 import com.intellij.openapi.application.impl.ApplicationImpl
 import com.intellij.openapi.components.StateStorage
-import com.intellij.openapi.components.impl.stores.FileStorage
-import com.intellij.openapi.components.impl.stores.IComponentStore
-import com.intellij.openapi.components.impl.stores.StoreUtil
 import com.intellij.openapi.components.stateStore
 import com.intellij.openapi.options.SchemesManagerFactory
 import com.intellij.openapi.progress.ProcessCanceledException
@@ -119,7 +114,7 @@ class SyncManager(private val icsManager: IcsManager, private val autoSyncManage
 
             icsManager.repositoryActive = true
             if (updateResult != null) {
-              restartApplication = updateStoragesFromStreamProvider(ApplicationManager.getApplication().stateStore, updateResult!!)
+              restartApplication = updateStoragesFromStreamProvider(ApplicationManager.getApplication().stateStore as ComponentStoreImpl, updateResult!!)
             }
             if (!restartApplication && syncType == SyncType.OVERWRITE_LOCAL) {
               (SchemesManagerFactory.getInstance() as SchemeManagerFactoryBase).process {
@@ -145,11 +140,9 @@ class SyncManager(private val icsManager: IcsManager, private val autoSyncManage
   }
 }
 
-private fun updateStoragesFromStreamProvider(store: IComponentStore, updateResult: UpdateResult): Boolean {
+private fun updateStoragesFromStreamProvider(store: ComponentStoreImpl, updateResult: UpdateResult): Boolean {
   val changedComponentNames = LinkedHashSet<String>()
-  val stateStorages = store.getStateStorageManager().getCachedFileStateStorages(updateResult.changed, updateResult.deleted)
-  val changed = stateStorages.first!!
-  val deleted = stateStorages.second!!
+  val (changed, deleted) = (store.storageManager as StateStorageManagerImpl).getCachedFileStorages(updateResult.changed, updateResult.deleted)
   if (changed.isEmpty() && deleted.isEmpty()) {
     return false
   }
@@ -170,7 +163,7 @@ private fun updateStoragesFromStreamProvider(store: IComponentStore, updateResul
 
         val changedStorageSet = THashSet<StateStorage>(changed)
         changedStorageSet.addAll(deleted)
-        (store as ComponentStoreImpl).reinitComponents(changedComponentNames, notReloadableComponents, changedStorageSet)
+        store.reinitComponents(changedComponentNames, changedStorageSet, notReloadableComponents)
       }
       finally {
         token.finish()
@@ -179,12 +172,12 @@ private fun updateStoragesFromStreamProvider(store: IComponentStore, updateResul
       if (notReloadableComponents.isEmpty()) {
         return false
       }
-      return StoreUtil.askToRestart(store, notReloadableComponents, null)
+      return askToRestart(store, notReloadableComponents, null, true)
     }
   })!!
 }
 
-private fun updateStateStorage(changedComponentNames: MutableSet<String>, stateStorages: Collection<FileStorage>, deleted: Boolean) {
+private fun updateStateStorage(changedComponentNames: MutableSet<String>, stateStorages: Collection<StateStorage>, deleted: Boolean) {
   for (stateStorage in stateStorages) {
     try {
       (stateStorage as XmlElementStorage).updatedFromStreamProvider(changedComponentNames, deleted)
index baad95b6600295d4211fe3e5ed933732a5f2ae27..7bf191383e50032583542858251fda1ce21a0f2c 100644 (file)
     <executeFile implementation="com.jetbrains.edu.coursecreator.PyCCRunTests"/>
   </extensions>
 
+  <extensions defaultExtensionNs="Pythonid">
+    <pyReferenceResolveProvider implementation="com.jetbrains.edu.coursecreator.PyCCReferenceResolveProvider"/>
+  </extensions>
+
   <application-components>
     <!-- Add your application components here -->
   </application-components>
index 3b8c4154f24b050db98a4b34d9937ff144dcec4a..08c0ed41ad26d6a6fd67907449cc9b4e2456dd35 100644 (file)
@@ -85,12 +85,12 @@ public class PyCCProjectGenerator extends PythonProjectGenerator implements Dire
         catch (Exception ignored) {
         }
         DirectoryUtil.createSubdirectories(EduNames.HINTS, projectDir, "\\/");
-        PsiDirectory lessonDir = CCCreateLesson.createLesson(null, project, baseDir, course);
+        PsiDirectory lessonDir = new CCCreateLesson().createItem(null, project, projectDir, course);
         if (lessonDir == null) {
           LOG.error("Failed to create lesson");
           return;
         }
-        CCCreateTask.createTask(null, project, lessonDir, false);
+        new CCCreateTask().createItem(null, project, lessonDir, course);
       }
     }.execute();
   }
diff --git a/python/edu/course-creator-python/src/com/jetbrains/edu/coursecreator/PyCCReferenceResolveProvider.java b/python/edu/course-creator-python/src/com/jetbrains/edu/coursecreator/PyCCReferenceResolveProvider.java
new file mode 100644 (file)
index 0000000..8e4d368
--- /dev/null
@@ -0,0 +1,23 @@
+package com.jetbrains.edu.coursecreator;
+
+import com.intellij.psi.PsiElement;
+import com.jetbrains.python.edu.PyEduUtils;
+import com.jetbrains.python.psi.PyQualifiedExpression;
+import com.jetbrains.python.psi.resolve.PyReferenceResolveProvider;
+import com.jetbrains.python.psi.resolve.RatedResolveResult;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+
+public class PyCCReferenceResolveProvider implements PyReferenceResolveProvider {
+  @NotNull
+  @Override
+  public List<RatedResolveResult> resolveName(@NotNull final PyQualifiedExpression element,
+                                              @NotNull final List<PsiElement> definers) {
+    if (CCProjectService.getInstance(element.getProject()).getCourse() == null) {
+      return Collections.emptyList();
+    }
+    return PyEduUtils.getResolveResultFromContainingDirectory(element, definers);
+  }
+}
index 1d2fc6ad288e705268ea5e6eb86ee644a3aefc0b..bfe179d2be5998bfa7402b049d4bb8dcefa13497 100644 (file)
@@ -18,5 +18,6 @@
     <orderEntry type="module" module-name="testFramework" scope="TEST" />
     <orderEntry type="library" scope="TEST" name="JUnit3" level="project" />
     <orderEntry type="module" module-name="educational" />
+    <orderEntry type="module" module-name="python-educational" />
   </component>
 </module>
\ No newline at end of file
index 2fe5d1fb1f2e421badc14f6708b0af1009c83a93..99cd52d452cc238862dde2bd95d0056b126b9c44 100644 (file)
@@ -23,6 +23,7 @@
   </extensions>
   <extensions defaultExtensionNs="Pythonid">
     <visitorFilter language="Python" implementationClass="com.jetbrains.edu.learning.highlighting.PyStudyVisitorFilter"/>
+    <pyReferenceResolveProvider implementation="com.jetbrains.edu.learning.PyStudyReferenceResolveProvider"/>
   </extensions>
   <extensions defaultExtensionNs="Edu">
     <StudyExecutor implementationClass="com.jetbrains.edu.learning.PyStudyExecutor" language="Python"/>
diff --git a/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyReferenceResolveProvider.java b/python/edu/interactive-learning-python/src/com/jetbrains/edu/learning/PyStudyReferenceResolveProvider.java
new file mode 100644 (file)
index 0000000..2fa19f0
--- /dev/null
@@ -0,0 +1,23 @@
+package com.jetbrains.edu.learning;
+
+import com.intellij.psi.PsiElement;
+import com.jetbrains.python.edu.PyEduUtils;
+import com.jetbrains.python.psi.PyQualifiedExpression;
+import com.jetbrains.python.psi.resolve.PyReferenceResolveProvider;
+import com.jetbrains.python.psi.resolve.RatedResolveResult;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+
+public class PyStudyReferenceResolveProvider implements PyReferenceResolveProvider {
+  @NotNull
+  @Override
+  public List<RatedResolveResult> resolveName(@NotNull final PyQualifiedExpression element,
+                                              @NotNull final List<PsiElement> definers) {
+    if (StudyTaskManager.getInstance(element.getProject()).getCourse() == null) {
+      return Collections.emptyList();
+    }
+    return PyEduUtils.getResolveResultFromContainingDirectory(element, definers);
+  }
+}
diff --git a/python/edu/src/com/jetbrains/python/edu/PyEduUtils.java b/python/edu/src/com/jetbrains/python/edu/PyEduUtils.java
new file mode 100644 (file)
index 0000000..14ddcbd
--- /dev/null
@@ -0,0 +1,32 @@
+package com.jetbrains.python.edu;
+
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.jetbrains.python.psi.PyQualifiedExpression;
+import com.jetbrains.python.psi.resolve.ImportedResolveResult;
+import com.jetbrains.python.psi.resolve.RatedResolveResult;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PyEduUtils {
+  private PyEduUtils() {
+  }
+
+  public static List<RatedResolveResult> getResolveResultFromContainingDirectory(@NotNull PyQualifiedExpression element,
+                                                                                 @NotNull List<PsiElement> definers) {
+    final List<RatedResolveResult> result = new ArrayList<RatedResolveResult>();
+    final PsiFile containingFile = element.getContainingFile();
+    if (containingFile == null) return result;
+    final PsiDirectory directory = containingFile.getContainingDirectory();
+    if (directory == null) return result;
+    final String elementName = element.getName();
+    final PsiFile file = directory.findFile(elementName + ".py");
+    if (file != null) {
+      result.add(new ImportedResolveResult(file, RatedResolveResult.RATE_NORMAL, definers));
+    }
+    return result;
+  }
+}
index c90e1ced953080c5839729106c91d7c0dbe529fa..03e400bd45461d7832e8d675f4da1d80ef594c6e 100644 (file)
@@ -48,9 +48,9 @@ class CCFileDeletedListener extends VirtualFileAdapter {
       return;
     }
     VirtualFile courseDir = myProject.getBaseDir();
-    CCUtils.updateHigherElements(courseDir.getChildren(), new Function<VirtualFile, StudyOrderable>() {
+    CCUtils.updateHigherElements(courseDir.getChildren(), new Function<VirtualFile, StudyItem>() {
       @Override
-      public StudyOrderable fun(VirtualFile file) {
+      public StudyItem fun(VirtualFile file) {
         return course.getLesson(file.getName());
       }
     }, removedLesson.getIndex(), EduNames.LESSON, -1);
@@ -70,9 +70,9 @@ class CCFileDeletedListener extends VirtualFileAdapter {
     if (task == null) {
       return;
     }
-    CCUtils.updateHigherElements(lessonDir.getChildren(), new Function<VirtualFile, StudyOrderable>() {
+    CCUtils.updateHigherElements(lessonDir.getChildren(), new Function<VirtualFile, StudyItem>() {
       @Override
-      public StudyOrderable fun(VirtualFile file) {
+      public StudyItem fun(VirtualFile file) {
         return lesson.getTask(file.getName());
       }
     }, task.getIndex(), EduNames.TASK, -1);
index 31007bdede6860cc71da6f0a09bd2c8a55f881d0..26703583931f95d6f11b3ed0c638e9cafedb6284 100644 (file)
@@ -13,10 +13,7 @@ import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
 import com.intellij.refactoring.rename.RenameHandler;
 import com.jetbrains.edu.EduNames;
-import com.jetbrains.edu.courseFormat.Course;
-import com.jetbrains.edu.courseFormat.Lesson;
-import com.jetbrains.edu.courseFormat.Named;
-import com.jetbrains.edu.courseFormat.Task;
+import com.jetbrains.edu.courseFormat.*;
 import org.jetbrains.annotations.NotNull;
 
 public class CCRenameHandler implements RenameHandler {
@@ -59,12 +56,12 @@ public class CCRenameHandler implements RenameHandler {
   }
 
 
-  private static void processRename(@NotNull final Named named, String namePrefix, @NotNull final Project project) {
-    String name = named.getName();
+  private static void processRename(@NotNull final StudyItem item, String namePrefix, @NotNull final Project project) {
+    String name = item.getName();
     String text = "Rename " + StringUtil.toTitleCase(namePrefix);
     String newName = Messages.showInputDialog(project, text + " '" + name + "' to", text, null, name, null);
     if (newName != null) {
-      named.setName(newName);
+      item.setName(newName);
     }
   }
 
index 9148afad4a3272af266ee89776e9e58ec9046d8d..650e43fb3220dff3706e0989aca6b8f5e83b27fe 100644 (file)
@@ -11,12 +11,15 @@ import com.intellij.psi.PsiFile;
 import com.intellij.util.Function;
 import com.jetbrains.edu.EduUtils;
 import com.jetbrains.edu.courseFormat.Course;
-import com.jetbrains.edu.courseFormat.StudyOrderable;
+import com.jetbrains.edu.courseFormat.StudyItem;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
 
 public class CCUtils {
   private static final Logger LOG = Logger.getInstance(CCUtils.class);
@@ -38,14 +41,13 @@ public class CCUtils {
   /**
    * This method decreases index and updates directory names of
    * all tasks/lessons that have higher index than specified object
-   *
    * @param dirs              directories that are used to get tasks/lessons
-   * @param getStudyOrderable function that is used to get task/lesson from VirtualFile. This function can return null
+   * @param getStudyItem function that is used to get task/lesson from VirtualFile. This function can return null
    * @param threshold         index is used as threshold
    * @param prefix            task or lesson directory name prefix
    */
   public static void updateHigherElements(VirtualFile[] dirs,
-                                          @NotNull final Function<VirtualFile, StudyOrderable> getStudyOrderable,
+                                          @NotNull final Function<VirtualFile, ? extends StudyItem> getStudyItem,
                                           final int threshold,
                                           final String prefix,
                                           final int delta) {
@@ -53,28 +55,28 @@ public class CCUtils {
       (Collections2.filter(Arrays.asList(dirs), new Predicate<VirtualFile>() {
         @Override
         public boolean apply(VirtualFile dir) {
-          final StudyOrderable orderable = getStudyOrderable.fun(dir);
-          if (orderable == null) {
+          final StudyItem item = getStudyItem.fun(dir);
+          if (item == null) {
             return false;
           }
-          int index = orderable.getIndex();
+          int index = item.getIndex();
           return index > threshold;
         }
       }));
     Collections.sort(dirsToRename, new Comparator<VirtualFile>() {
       @Override
       public int compare(VirtualFile o1, VirtualFile o2) {
-        StudyOrderable orderable1 = getStudyOrderable.fun(o1);
-        StudyOrderable orderable2 = getStudyOrderable.fun(o2);
+        StudyItem item1 = getStudyItem.fun(o1);
+        StudyItem item2 = getStudyItem.fun(o2);
         //if we delete some dir we should start increasing numbers in dir names from the end
-        return (-delta) * EduUtils.INDEX_COMPARATOR.compare(orderable1, orderable2);
+        return (-delta) * EduUtils.INDEX_COMPARATOR.compare(item1, item2);
       }
     });
 
     for (final VirtualFile dir : dirsToRename) {
-      final StudyOrderable orderable = getStudyOrderable.fun(dir);
-      final int newIndex = orderable.getIndex() + delta;
-      orderable.setIndex(newIndex);
+      final StudyItem item = getStudyItem.fun(dir);
+      final int newIndex = item.getIndex() + delta;
+      item.setIndex(newIndex);
       ApplicationManager.getApplication().runWriteAction(new Runnable() {
         @Override
         public void run() {
index 7259d34afa8023a45daa9a049a3637047516b1a1..962b2728129c03eb0cbbb752873643951c34a9a3 100644 (file)
 package com.jetbrains.edu.coursecreator.actions;
 
 import com.intellij.ide.IdeView;
-import com.intellij.ide.util.DirectoryChooserUtil;
 import com.intellij.ide.util.DirectoryUtil;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.CommonDataKeys;
-import com.intellij.openapi.actionSystem.LangDataKeys;
 import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.project.DumbAwareAction;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.DialogWrapper;
-import com.intellij.openapi.ui.Messages;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiDirectory;
 import com.intellij.psi.PsiManager;
 import com.intellij.util.Function;
 import com.intellij.util.PlatformIcons;
 import com.jetbrains.edu.EduNames;
-import com.jetbrains.edu.EduUtils;
 import com.jetbrains.edu.courseFormat.Course;
 import com.jetbrains.edu.courseFormat.Lesson;
-import com.jetbrains.edu.courseFormat.StudyOrderable;
-import com.jetbrains.edu.coursecreator.CCProjectService;
-import com.jetbrains.edu.coursecreator.CCUtils;
-import com.jetbrains.edu.coursecreator.ui.CCCreateStudyItemDialog;
+import com.jetbrains.edu.courseFormat.StudyItem;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.Collections;
+import java.util.List;
 
-public class CCCreateLesson extends DumbAwareAction {
+public class CCCreateLesson extends CCCreateStudyItemActionBase {
   public static final String TITLE = "Create New " + EduNames.LESSON_TITLED;
 
   public CCCreateLesson() {
     super(EduNames.LESSON_TITLED, TITLE, PlatformIcons.DIRECTORY_CLOSED_ICON);
   }
 
+  @Nullable
   @Override
-  public void actionPerformed(AnActionEvent e) {
-    final IdeView view = e.getData(LangDataKeys.IDE_VIEW);
-    final Project project = e.getData(CommonDataKeys.PROJECT);
-    if (view == null || project == null) {
-      return;
-    }
-    final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view);
-    if (directory == null) return;
-    final CCProjectService service = CCProjectService.getInstance(project);
-    final Course course = service.getCourse();
-    if (course == null) {
-      return;
-    }
-    createLesson(view, project, directory.getVirtualFile(), course);
+  protected PsiDirectory getParentDir(@NotNull Project project, @NotNull Course course, @NotNull PsiDirectory directory) {
+    return PsiManager.getInstance(project).findDirectory(project.getBaseDir());
   }
 
-  @Nullable
-  public static PsiDirectory createLesson(@Nullable IdeView view, @NotNull final Project project,
-                                          @NotNull final VirtualFile directory, @NotNull final Course course) {
-    Lesson lesson = getLesson(directory, project, course, view);
-    if (lesson == null) {
-      return null;
-    }
-    VirtualFile courseDir = project.getBaseDir();
-    int lessonIndex = lesson.getIndex();
-    CCUtils.updateHigherElements(courseDir.getChildren(), new Function<VirtualFile, StudyOrderable>() {
-      @Override
-      public StudyOrderable fun(VirtualFile file) {
-        return course.getLesson(file.getName());
-      }
-    }, lessonIndex - 1, EduNames.LESSON, 1);
-    course.addLesson(lesson);
-    Collections.sort(course.getLessons(), EduUtils.INDEX_COMPARATOR);
-    return createLessonDir(project, lessonIndex, view);
+  @Override
+  protected void addItem(@NotNull Course course, @NotNull StudyItem item) {
+    course.addLesson(((Lesson)item));
   }
 
-  @Nullable
-  private static Lesson getLesson(@NotNull final VirtualFile sourceDirectory,
-                                  @NotNull final Project project,
-                                  @NotNull final Course course,
-                                  @Nullable IdeView view) {
-
-    VirtualFile courseDir = project.getBaseDir();
-    String lessonName;
-    int lessonIndex;
-    if (sourceDirectory.equals(courseDir)) {
-      lessonIndex = course.getLessons().size() + 1;
-      String suggestedName = EduNames.LESSON + lessonIndex;
-      lessonName = view == null ? suggestedName : Messages.showInputDialog("Name:", TITLE, null, suggestedName, null);
-    } else {
-      Lesson sourceLesson = course.getLesson(sourceDirectory.getName());
-      if (sourceLesson == null) {
-        return null;
-      }
-      final int index = sourceLesson.getIndex();
-      CCCreateStudyItemDialog dialog = new CCCreateStudyItemDialog(project, EduNames.LESSON, sourceLesson.getName(), index);
-      dialog.show();
-      if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
+  @Override
+  protected Function<VirtualFile, ? extends StudyItem> getStudyOrderable(@NotNull final StudyItem item) {
+    return new Function<VirtualFile, StudyItem>() {
+      @Override
+      public StudyItem fun(VirtualFile file) {
+        if (item instanceof Lesson) {
+          return ((Lesson)item).getCourse().getLesson(file.getName());
+        }
         return null;
       }
-      lessonName = dialog.getName();
-      lessonIndex = index + dialog.getIndexDelta();
-    }
-    if (lessonName == null) {
-      return null;
-    }
-    return createAndInitLesson(course, lessonName, lessonIndex);
-  }
-
-  @NotNull
-  private static Lesson createAndInitLesson(@NotNull final Course course, @NotNull String lessonName, int lessonIndex) {
-    Lesson lesson = new Lesson();
-    lesson.setName(lessonName);
-    lesson.setCourse(course);
-    lesson.setIndex(lessonIndex);
-    return lesson;
+    };
   }
 
+  @Override
   @Nullable
-  public static PsiDirectory createLessonDir(@NotNull final Project project, final int index, final IdeView view) {
-    final PsiDirectory projectDir = PsiManager.getInstance(project).findDirectory(project.getBaseDir());
+  protected PsiDirectory createItemDir(@NotNull final Project project, @NotNull final StudyItem item,
+                                    @Nullable final IdeView view, @NotNull final PsiDirectory parentDirectory,
+                                    @NotNull final Course course) {
     final PsiDirectory[] lessonDirectory = new PsiDirectory[1];
     ApplicationManager.getApplication().runWriteAction(new Runnable() {
       @Override
       public void run() {
-        lessonDirectory[0] = DirectoryUtil.createSubdirectories(EduNames.LESSON + index, projectDir, "\\/");
+        lessonDirectory[0] = DirectoryUtil.createSubdirectories(EduNames.LESSON + item.getIndex(), parentDirectory, "\\/");
       }
     });
     if (lessonDirectory[0] != null) {
@@ -134,32 +70,45 @@ public class CCCreateLesson extends DumbAwareAction {
   }
 
   @Override
-  public void update(@NotNull AnActionEvent event) {
-    if (!CCProjectService.setCCActionAvailable(event)) {
-      return;
-    }
-    final Project project = event.getData(CommonDataKeys.PROJECT);
-    final IdeView view = event.getData(LangDataKeys.IDE_VIEW);
-    if (project == null || view == null) {
-      EduUtils.enableAction(event, false);
-      return;
-    }
-    final PsiDirectory[] directories = view.getDirectories();
-    if (directories.length == 0) {
-      EduUtils.enableAction(event, false);
-      return;
-    }
-    final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view);
-    if (directory != null && project.getBaseDir().equals(directory.getVirtualFile())) {
-      EduUtils.enableAction(event, true);
-      return;
-    }
-    final CCProjectService service = CCProjectService.getInstance(project);
-    Course course = service.getCourse();
-    if (directory != null && course != null && course.getLesson(directory.getName()) != null) {
-      EduUtils.enableAction(event, true);
-      return;
-    }
-    EduUtils.enableAction(event, false);
+  protected int getSiblingsSize(@NotNull Course course, @Nullable StudyItem item) {
+    return course.getLessons().size();
+  }
+
+  @Nullable
+  @Override
+  protected StudyItem getParentItem(@NotNull Course course, @NotNull PsiDirectory directory) {
+    return null;
+  }
+
+  @Nullable
+  @Override
+  protected StudyItem getThresholdItem(@NotNull final Course course, @NotNull final PsiDirectory sourceDirectory) {
+    return course.getLesson(sourceDirectory.getName());
+  }
+
+  @Override
+  protected boolean isAddedAsLast(@NotNull PsiDirectory sourceDirectory,
+                                  @NotNull Project project,
+                                  @NotNull Course course) {
+    return sourceDirectory.getVirtualFile().equals(project.getBaseDir());
+  }
+
+  @Override
+  protected List<? extends StudyItem> getSiblings(@NotNull Course course, @Nullable StudyItem parentItem) {
+    return course.getLessons();
+  }
+
+  @Override
+  protected String getItemName() {
+    return EduNames.LESSON;
+  }
+
+  @Override
+  protected StudyItem createAndInitItem(@NotNull Course course, @Nullable StudyItem parentItem, String name, int index) {
+    Lesson lesson = new Lesson();
+    lesson.setName(name);
+    lesson.setCourse(course);
+    lesson.setIndex(index);
+    return lesson;
   }
 }
\ No newline at end of file
diff --git a/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCCreateStudyItemActionBase.java b/python/educational/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCCreateStudyItemActionBase.java
new file mode 100644 (file)
index 0000000..185fe10
--- /dev/null
@@ -0,0 +1,183 @@
+package com.jetbrains.edu.coursecreator.actions;
+
+import com.intellij.ide.IdeView;
+import com.intellij.ide.util.DirectoryChooserUtil;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.util.Function;
+import com.jetbrains.edu.EduUtils;
+import com.jetbrains.edu.courseFormat.Course;
+import com.jetbrains.edu.courseFormat.StudyItem;
+import com.jetbrains.edu.coursecreator.CCProjectService;
+import com.jetbrains.edu.coursecreator.CCUtils;
+import com.jetbrains.edu.coursecreator.ui.CCCreateStudyItemDialog;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class CCCreateStudyItemActionBase extends DumbAwareAction {
+
+  public CCCreateStudyItemActionBase(String text, String description, Icon icon) {
+    super(text, description, icon);
+  }
+
+
+  @Override
+  public void actionPerformed(AnActionEvent e) {
+    final IdeView view = e.getData(LangDataKeys.IDE_VIEW);
+    final Project project = e.getData(CommonDataKeys.PROJECT);
+    if (view == null || project == null) {
+      return;
+    }
+    final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view);
+    if (directory == null) return;
+    final CCProjectService service = CCProjectService.getInstance(project);
+    final Course course = service.getCourse();
+    if (course == null) {
+      return;
+    }
+    createItem(view, project, directory, course);
+  }
+
+
+  @Override
+  public void update(@NotNull AnActionEvent event) {
+    if (!CCProjectService.setCCActionAvailable(event)) {
+      return;
+    }
+    final Presentation presentation = event.getPresentation();
+    final Project project = event.getData(CommonDataKeys.PROJECT);
+    final IdeView view = event.getData(LangDataKeys.IDE_VIEW);
+    if (project == null || view == null) {
+      presentation.setEnabledAndVisible(false);
+      return;
+    }
+    final PsiDirectory[] directories = view.getDirectories();
+    if (directories.length == 0) {
+      presentation.setEnabledAndVisible(false);
+      return;
+    }
+    final PsiDirectory sourceDirectory = DirectoryChooserUtil.getOrChooseDirectory(view);
+    final CCProjectService service = CCProjectService.getInstance(project);
+    final Course course = service.getCourse();
+    if (course == null || sourceDirectory == null) {
+      presentation.setEnabledAndVisible(false);
+      return;
+    }
+    if (!isAddedAsLast(sourceDirectory, project, course) &&
+        getThresholdItem(course, sourceDirectory) == null) {
+      presentation.setEnabledAndVisible(false);
+    }
+    if (CommonDataKeys.PSI_FILE.getData(event.getDataContext()) != null) {
+      presentation.setEnabledAndVisible(false);
+    }
+  }
+
+
+  @Nullable
+  protected abstract PsiDirectory getParentDir(@NotNull final Project project,
+                                          @NotNull final Course course,
+                                          @NotNull final PsiDirectory directory);
+
+
+  @Nullable
+  public PsiDirectory createItem(@Nullable final IdeView view, @NotNull final Project project,
+                                             @NotNull final PsiDirectory sourceDirectory, @NotNull final Course course) {
+    StudyItem parentItem = getParentItem(course, sourceDirectory);
+    final StudyItem item = getItem(sourceDirectory, project, course, view, parentItem);
+    if (item == null) {
+      return null;
+    }
+    final PsiDirectory parentDir = getParentDir(project, course, sourceDirectory);
+    if (parentDir == null) {
+      return null;
+    }
+    CCUtils.updateHigherElements(parentDir.getVirtualFile().getChildren(), getStudyOrderable(item),
+                                 item.getIndex() - 1, getItemName(), 1);
+    addItem(course, item);
+    Collections.sort(getSiblings(course, parentItem), EduUtils.INDEX_COMPARATOR);
+    return createItemDir(project, item, view, parentDir, course);
+  }
+
+  protected abstract void addItem(@NotNull final Course course, @NotNull final StudyItem item);
+
+
+  protected abstract Function<VirtualFile, ? extends StudyItem> getStudyOrderable(@NotNull final StudyItem item);
+
+  protected abstract PsiDirectory createItemDir(@NotNull final Project project, @NotNull final StudyItem item,
+                                                @Nullable final IdeView view, @NotNull final PsiDirectory parentDirectory,
+                                                @NotNull final Course course);
+
+  @Nullable
+  protected StudyItem getItem(@NotNull final PsiDirectory sourceDirectory,
+                              @NotNull final Project project,
+                              @NotNull final Course course,
+                              @Nullable IdeView view,
+                              @Nullable StudyItem parentItem) {
+
+    String itemName;
+    int itemIndex;
+    if (isAddedAsLast(sourceDirectory, project, course)) {
+      itemIndex = getSiblingsSize(course, parentItem) + 1;
+      String suggestedName = getItemName() + itemIndex;
+      itemName = view == null ? suggestedName : Messages.showInputDialog("Name:", getTitle(),
+                                                                         null, suggestedName, null);
+    }
+    else {
+      StudyItem thresholdItem = getThresholdItem(course, sourceDirectory);
+      if (thresholdItem == null) {
+        return null;
+      }
+      final int index = thresholdItem.getIndex();
+      CCCreateStudyItemDialog dialog = new CCCreateStudyItemDialog(project, getItemName(), thresholdItem.getName(), index);
+      dialog.show();
+      if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
+        return null;
+      }
+      itemName = dialog.getName();
+      itemIndex = index + dialog.getIndexDelta();
+    }
+    if (itemName == null) {
+      return null;
+    }
+    return createAndInitItem(course, parentItem, itemName, itemIndex);
+  }
+
+  protected abstract int getSiblingsSize(@NotNull final Course course, @Nullable final StudyItem parentItem);
+
+  @NotNull
+  protected String getTitle() {
+    return "Create New " + StringUtil.toTitleCase(getItemName());
+  }
+
+  @Nullable
+  protected abstract StudyItem getParentItem(@NotNull final Course course, @NotNull final PsiDirectory directory);
+
+  @Nullable
+  protected abstract StudyItem getThresholdItem(@NotNull final Course course, @NotNull final PsiDirectory sourceDirectory);
+
+  protected abstract boolean isAddedAsLast(@NotNull final PsiDirectory sourceDirectory,
+                                           @NotNull final Project project,
+                                           @NotNull final Course course);
+
+  protected abstract List<? extends StudyItem> getSiblings(@NotNull final Course course, @Nullable final StudyItem parentItem);
+
+  protected abstract String getItemName();
+
+  protected abstract StudyItem createAndInitItem(@NotNull final Course course,
+                                                 @Nullable final StudyItem parentItem,
+                                                 String name,
+                                                 int index);
+}
index 8a68ff6ace9ef2533304a6428b137fac1c7f976a..ddbaba2ada1bcf19f2a6bc6301ceb1fd6dc02c88 100644 (file)
@@ -4,165 +4,179 @@ import com.intellij.ide.IdeView;
 import com.intellij.ide.fileTemplates.FileTemplate;
 import com.intellij.ide.fileTemplates.FileTemplateManager;
 import com.intellij.ide.fileTemplates.FileTemplateUtil;
-import com.intellij.ide.util.DirectoryChooserUtil;
 import com.intellij.ide.util.DirectoryUtil;
 import com.intellij.ide.util.EditorHelper;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.CommonDataKeys;
-import com.intellij.openapi.actionSystem.LangDataKeys;
-import com.intellij.openapi.actionSystem.Presentation;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.fileEditor.FileEditorManager;
-import com.intellij.openapi.project.DumbAwareAction;
 import com.intellij.openapi.project.Project;
-import com.intellij.openapi.ui.Messages;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiDirectory;
 import com.intellij.psi.PsiElement;
+import com.intellij.util.Function;
 import com.intellij.util.PlatformIcons;
 import com.jetbrains.edu.EduNames;
 import com.jetbrains.edu.courseFormat.Course;
 import com.jetbrains.edu.courseFormat.Lesson;
+import com.jetbrains.edu.courseFormat.StudyItem;
 import com.jetbrains.edu.courseFormat.Task;
 import com.jetbrains.edu.coursecreator.CCLanguageManager;
-import com.jetbrains.edu.coursecreator.CCProjectService;
 import com.jetbrains.edu.coursecreator.CCUtils;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-public class CCCreateTask extends DumbAwareAction {
+import java.util.Collections;
+import java.util.List;
+
+public class CCCreateTask extends CCCreateStudyItemActionBase {
   private static final Logger LOG = Logger.getInstance(CCCreateTask.class.getName());
+  public static final String TITLE = "Create New " + EduNames.TASK_TITLED;
 
   public CCCreateTask() {
-    super("Task", "Create new Task", PlatformIcons.DIRECTORY_CLOSED_ICON);
+    super(EduNames.TASK_TITLED, TITLE, PlatformIcons.DIRECTORY_CLOSED_ICON);
   }
 
-  @Override
-  public void actionPerformed(final AnActionEvent e) {
-    final IdeView view = e.getData(LangDataKeys.IDE_VIEW);
-    final Project project = e.getData(CommonDataKeys.PROJECT);
 
-    if (project == null || view == null) {
+  private static void createFromTemplateAndOpen(@NotNull final PsiDirectory taskDirectory,
+                                                @Nullable final FileTemplate template,
+                                                @Nullable IdeView view) {
+    if (template == null) {
       return;
     }
-    final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view);
-
-    if (directory == null) return;
-    createTask(view, project, directory, true);
+    try {
+      final PsiElement file = FileTemplateUtil.createFromTemplate(template, template.getName(), null, taskDirectory);
+      if (view != null) {
+        EditorHelper.openInEditor(file, false);
+        view.selectElement(file);
+      }
+    }
+    catch (Exception e) {
+      LOG.error(e);
+    }
   }
 
-  public static void createTask(final IdeView view, final Project project, final PsiDirectory lessonDir, boolean showDialog) {
-    final CCProjectService service = CCProjectService.getInstance(project);
-    final Course course = service.getCourse();
-    final Lesson lesson = course.getLesson(lessonDir.getName());
-    final int size = lesson.getTaskList().size();
-    final String taskName;
-    if (showDialog) {
-      taskName = Messages.showInputDialog("Name:", "Task Name", null, EduNames.TASK + (size + 1), null);
-    }
-    else {
-      taskName = EduNames.TASK + (size + 1);
+  @Nullable
+  @Override
+  protected PsiDirectory getParentDir(@NotNull Project project, @NotNull Course course, @NotNull PsiDirectory directory) {
+    if (isAddedAsLast(directory, project, course)) {
+      return directory;
     }
+    return directory.getParent();
+  }
 
-    if (taskName == null) {
-      return;
+  @Override
+  protected void addItem(@NotNull Course course, @NotNull StudyItem item) {
+    if (item instanceof Task) {
+      Task task = (Task)item;
+      task.getLesson().addTask(task);
     }
+  }
+
+  @Override
+  protected Function<VirtualFile, ? extends StudyItem> getStudyOrderable(@NotNull final StudyItem item) {
+    return new Function<VirtualFile, StudyItem>() {
+      @Override
+      public StudyItem fun(VirtualFile file) {
+        if (item instanceof Task) {
+          return ((Task)item).getLesson().getTask(file.getName());
+        }
+        return null;
+      }
+    };
+  }
+
+  @Override
+  @Nullable
+  protected PsiDirectory createItemDir(@NotNull final Project project, @NotNull final StudyItem item,
+                                     @Nullable final IdeView view, @NotNull final PsiDirectory parentDirectory,
+                                     @NotNull final Course course) {
+    final PsiDirectory[] taskDirectory = new PsiDirectory[1];
     ApplicationManager.getApplication().runWriteAction(new Runnable() {
       @Override
       public void run() {
-        final PsiDirectory taskDirectory = DirectoryUtil.createSubdirectories(EduNames.TASK + (size + 1), lessonDir, "\\/");
-        if (taskDirectory != null) {
+        taskDirectory[0] = DirectoryUtil.createSubdirectories(EduNames.TASK + item.getIndex(), parentDirectory, "\\/");
+        if (taskDirectory[0] != null) {
           CCLanguageManager manager = CCUtils.getStudyLanguageManager(course);
           if (manager == null) {
             return;
           }
-
-          final Task task = new Task(taskName);
-          task.setIndex(size + 1);
-          lesson.addTask(task);
-
-          createFromTemplateAndOpen(taskDirectory, manager.getTestsTemplate(project), view);
-          createFromTemplateAndOpen(taskDirectory, FileTemplateManager.getInstance(project).getInternalTemplate("task.html"), view);
+          createFromTemplateAndOpen(taskDirectory[0], manager.getTestsTemplate(project), view);
+          createFromTemplateAndOpen(taskDirectory[0], FileTemplateManager.getInstance(project).getInternalTemplate("task.html"), view);
           String defaultExtension = manager.getDefaultTaskFileExtension();
           if (defaultExtension != null) {
             FileTemplate taskFileTemplate = manager.getTaskFileTemplateForExtension(project, defaultExtension);
-            createFromTemplateAndOpen(taskDirectory, taskFileTemplate, view);
+            createFromTemplateAndOpen(taskDirectory[0], taskFileTemplate, view);
             if (taskFileTemplate != null) {
               String taskFileName = FileUtil.getNameWithoutExtension(taskFileTemplate.getName());
-              task.addTaskFile(taskFileName + "." + defaultExtension, size + 1);
+              ((Task)item).addTaskFile(taskFileName + "." + defaultExtension, 1);
             }
           }
-
-          ApplicationManager.getApplication().invokeLater(new Runnable() {
-            @Override
-            public void run() {
-              FileEditorManager fileEditorManager = FileEditorManager.getInstance(project);
-              for (VirtualFile virtualFile : fileEditorManager.getOpenFiles()) {
-                fileEditorManager.closeFile(virtualFile);
-              }
-            }
-          });
         }
       }
     });
+    return taskDirectory[0];
   }
 
-  private static void createFromTemplateAndOpen(@NotNull final PsiDirectory taskDirectory,
-                                                @Nullable final FileTemplate template,
-                                                @Nullable IdeView view) {
-    if (template == null) {
-      return;
-    }
-    try {
-      final PsiElement file = FileTemplateUtil.createFromTemplate(template, template.getName(), null, taskDirectory);
-      if (view != null) {
-        EditorHelper.openInEditor(file, false);
-        view.selectElement(file);
-      }
+  @Override
+  protected int getSiblingsSize(@NotNull Course course, @Nullable StudyItem parentItem) {
+    if (parentItem instanceof Lesson) {
+      return ((Lesson)parentItem).getTaskList().size();
     }
-    catch (Exception e) {
-      LOG.error(e);
+    return 0;
+  }
+
+  @Nullable
+  @Override
+  protected StudyItem getParentItem(@NotNull Course course, @NotNull PsiDirectory directory) {
+    Task task = (Task)getThresholdItem(course, directory);
+    if (task == null) {
+      return course.getLesson(directory.getName());
     }
+    return task.getLesson();
   }
 
+  @Nullable
   @Override
-  public void update(@NotNull AnActionEvent event) {
-    if (!CCProjectService.setCCActionAvailable(event)) {
-      return;
+  protected StudyItem getThresholdItem(@NotNull Course course, @NotNull PsiDirectory sourceDirectory) {
+    PsiDirectory parent = sourceDirectory.getParent();
+    if (parent == null) {
+      return null;
     }
-    final Presentation presentation = event.getPresentation();
-    final Project project = event.getData(CommonDataKeys.PROJECT);
-    if (project == null) {
-      presentation.setVisible(false);
-      presentation.setEnabled(false);
-      return;
+    Lesson lesson = course.getLesson(parent.getName());
+    if (lesson == null) {
+      return null;
     }
+    return lesson.getTask(sourceDirectory.getName());
+  }
 
-    final IdeView view = event.getData(LangDataKeys.IDE_VIEW);
-    if (view == null) {
-      presentation.setVisible(false);
-      presentation.setEnabled(false);
-      return;
-    }
+  @Override
+  protected boolean isAddedAsLast(@NotNull PsiDirectory sourceDirectory,
+                                  @NotNull Project project,
+                                  @NotNull Course course) {
+    return course.getLesson(sourceDirectory.getName()) != null;
+  }
 
-    final PsiDirectory[] directories = view.getDirectories();
-    if (directories.length == 0) {
-      presentation.setVisible(false);
-      presentation.setEnabled(false);
-      return;
-    }
-    final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view);
-    final CCProjectService service = CCProjectService.getInstance(project);
-    final Course course = service.getCourse();
-    if (course != null && directory != null && course.getLesson(directory.getName()) == null) {
-      presentation.setVisible(false);
-      presentation.setEnabled(false);
-      return;
+  @Override
+  protected List<? extends StudyItem> getSiblings(@NotNull Course course, @Nullable StudyItem parentItem) {
+    if (parentItem instanceof Lesson) {
+      return ((Lesson)parentItem).getTaskList();
     }
+    return Collections.emptyList();
+  }
+
+  @Override
+  protected String getItemName() {
+    return EduNames.TASK;
+  }
 
-    presentation.setVisible(true);
-    presentation.setEnabled(true);
+  @Override
+  protected StudyItem createAndInitItem(@NotNull Course course, @Nullable StudyItem parentItem, String name, int index) {
+    final Task task = new Task(name);
+    task.setIndex(index);
+    if (parentItem == null) {
+      return null;
+    }
+    task.setLesson(((Lesson)parentItem));
+    return task;
   }
 }
\ No newline at end of file
index e6b2fd99a427266ee25e29b1873cdaca2e54bae1..344f01cc338438147ce1b19346dda1289caaf59c 100644 (file)
@@ -24,13 +24,14 @@ public class EduNames {
   public static final String HINTS = "hints";
   public static final String LESSON = "lesson";
   public static final String LESSON_TITLED = StringUtil.toTitleCase(LESSON);
+  public static final String TASK = "task";
+  public static final String TASK_TITLED = StringUtil.toTitleCase(TASK);
   public static final String COURSE = "course";
   public static final String TEST_TAB_NAME = "test";
   public static final String USER_TEST_INPUT = "input";
   public static final String USER_TEST_OUTPUT = "output";
   public static final String WINDOW_POSTFIX = "_window.";
   public static final String WINDOWS_POSTFIX = "_windows";
-  public static final String TASK = "task";
   public static final String USER_TESTS = "userTests";
   public static final String TESTS_FILE = "tests.py";
   public static final String TEST_HELPER = "test_helper.py";
index 852921eb5709c450a327c806c7e372e2aaceea12..ec743b0eba9fe82ef72bbd80feb444469e889f2b 100644 (file)
@@ -13,7 +13,7 @@ import com.intellij.openapi.util.TextRange;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.vfs.VirtualFileManager;
 import com.jetbrains.edu.courseFormat.AnswerPlaceholder;
-import com.jetbrains.edu.courseFormat.StudyOrderable;
+import com.jetbrains.edu.courseFormat.StudyItem;
 import com.jetbrains.edu.courseFormat.Task;
 import com.jetbrains.edu.courseFormat.TaskFile;
 import org.jetbrains.annotations.NotNull;
@@ -31,9 +31,9 @@ public class EduUtils {
   }
   private static final Logger LOG = Logger.getInstance(EduUtils.class.getName());
 
-  public static Comparator<StudyOrderable> INDEX_COMPARATOR = new Comparator<StudyOrderable>() {
+  public static Comparator<StudyItem> INDEX_COMPARATOR = new Comparator<StudyItem>() {
     @Override
-    public int compare(StudyOrderable o1, StudyOrderable o2) {
+    public int compare(StudyItem o1, StudyItem o2) {
       return o1.getIndex() - o2.getIndex();
     }
   };
index b71df1b5fb5f28db8009ed163d80e895ae36d1f5..a2ffd42ef345fd1a0b0de73e0107b43d9ddb50e1 100644 (file)
@@ -11,7 +11,7 @@ import org.jetbrains.annotations.Nullable;
  * Implementation of windows which user should type in
  */
 
-public class AnswerPlaceholder implements StudyOrderable {
+public class AnswerPlaceholder {
 
   @Expose private int line = 0;
   @Expose private int start = 0;
index f891962d084aadb7fd72b0b419f73bea03336887..6f4b07c2feb5a2d854baead1c6de6f92cb085ede 100644 (file)
@@ -13,7 +13,7 @@ import org.jetbrains.annotations.NotNull;
 import java.util.ArrayList;
 import java.util.List;
 
-public class Course implements Named {
+public class Course {
   @Expose
   private List<Lesson> lessons = new ArrayList<Lesson>();
 
index 5c7d9ceb2745a0aa06bee19e7d0ac85c273fa14c..dd46f939f077f4ffa36b73f6fb847b4aa250984d 100644 (file)
@@ -10,7 +10,7 @@ import org.jetbrains.annotations.NotNull;
 import java.util.ArrayList;
 import java.util.List;
 
-public class Lesson implements Named, StudyOrderable {
+public class Lesson implements StudyItem {
   @Transient
   public int id;
   @Transient
similarity index 55%
rename from python/educational/src/com/jetbrains/edu/courseFormat/Named.java
rename to python/educational/src/com/jetbrains/edu/courseFormat/StudyItem.java
index f7287cfd6dd3471172d242a7cf6ac0e148787d84..b09d2570a687fe43dc9e246010da5320dde7581d 100644 (file)
@@ -1,6 +1,8 @@
 package com.jetbrains.edu.courseFormat;
 
-public interface Named {
+public interface StudyItem {
   String getName();
   void setName(String name);
+  int getIndex();
+  void setIndex(int index);
 }
diff --git a/python/educational/src/com/jetbrains/edu/courseFormat/StudyOrderable.java b/python/educational/src/com/jetbrains/edu/courseFormat/StudyOrderable.java
deleted file mode 100644 (file)
index 1d773e3..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.jetbrains.edu.courseFormat;
-
-public interface StudyOrderable {
-  int getIndex();
-  void setIndex(int index);
-}
index 07a902d318c6dda1050fa54b44a3bac989776de4..7c6c97fff06b0c44d230cdb6b0b7eb4fcf8a8220 100644 (file)
@@ -18,7 +18,7 @@ import java.util.Map;
 /**
  * Implementation of task which contains task files, tests, input file for tests
  */
-public class Task implements Named, StudyOrderable {
+public class Task implements StudyItem {
   @Expose
   private String name;
 
index 12084a909affb1313cb3ae42a074173edda9f069..90368be9b9baf1757cc3bbf65672f8c93d87204b 100644 (file)
@@ -17,7 +17,7 @@ import java.util.List;
  * which is visible to student in project view
  */
 
-public class TaskFile implements StudyOrderable {
+public class TaskFile {
   @SerializedName("placeholders")
   @Expose
   private List<AnswerPlaceholder> myAnswerPlaceholders = new ArrayList<AnswerPlaceholder>();
index b7d17c74cfaa51a20f52429bc3dbfaa7b3f350fd..dd8d0058eb662a5310297646265628e40ebdf07f 100644 (file)
@@ -1,6 +1,5 @@
 package org.jetbrains.plugins.ipnb.configuration;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.intellij.execution.ExecutionException;
 import com.intellij.execution.RunContentExecutor;
@@ -28,7 +27,6 @@ import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.ui.HyperlinkAdapter;
 import com.intellij.util.Alarm;
 import com.intellij.util.io.HttpRequests;
-import com.jetbrains.python.PythonHelpersLocator;
 import com.jetbrains.python.packaging.PyPackage;
 import com.jetbrains.python.packaging.PyPackageManager;
 import com.jetbrains.python.sdk.PythonSdkType;
@@ -263,7 +261,6 @@ public final class IpnbConnectionManager implements ProjectComponent {
     }
     catch (ExecutionException ignored) {
     }
-    final Map<String, String> env = ImmutableMap.of("PYCHARM_EP_DIST", "ipython", "PYCHARM_EP_NAME", "ipython");
 
     final Pair<String, String> hostPort = getHostPortFromUrl(url);
     if (hostPort == null) {
@@ -271,8 +268,10 @@ public final class IpnbConnectionManager implements ProjectComponent {
                   new IpnbSettingsAdapter());
       return false;
     }
-    final String ipython = PythonHelpersLocator.getHelperPath("pycharm/pycharm_load_entry_point.py");
-    final ArrayList<String> parameters = Lists.newArrayList(sdk.getHomePath(), ipython, "notebook", "--no-browser");
+    final String homePath = sdk.getHomePath();
+    if (homePath == null) return false;
+    final String ipython = PythonSdkType.getExecutablePath(homePath, "ipython");
+    final ArrayList<String> parameters = Lists.newArrayList(homePath, ipython, "notebook", "--no-browser");
     if (hostPort.getFirst() != null) {
       parameters.add("--ip");
       parameters.add(hostPort.getFirst());
@@ -281,8 +280,7 @@ public final class IpnbConnectionManager implements ProjectComponent {
       parameters.add("--port");
       parameters.add(hostPort.getSecond());
     }
-    final GeneralCommandLine commandLine = new GeneralCommandLine(parameters).withWorkDirectory(myProject.getBasePath()).
-      withEnvironment(env);
+    final GeneralCommandLine commandLine = new GeneralCommandLine(parameters).withWorkDirectory(myProject.getBasePath());
 
     try {
       final KillableColoredProcessHandler processHandler = new KillableColoredProcessHandler(commandLine) {
index 2c0f8018407c8b004c25fbda52060ed5f3bd4cb1..bd1d0ad4ec2445d775654a6005f3cd1f4068770c 100644 (file)
@@ -38,6 +38,8 @@ import com.intellij.openapi.module.ModuleUtilCore;
 import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.progress.ProgressManager;
 import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.project.DumbModePermission;
+import com.intellij.openapi.project.DumbService;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.projectRoots.*;
 import com.intellij.openapi.roots.ModuleRootManager;
@@ -496,14 +498,20 @@ public class PythonSdkType extends SdkType {
   public void setupSdkPaths(@NotNull final Sdk sdk) {
     final Project project;
     final WeakReference<Component> ownerComponentRef = sdk.getUserData(SDK_CREATOR_COMPONENT_KEY);
-    Component ownerComponent = SoftReference.dereference(ownerComponentRef);
+    final Component ownerComponent = SoftReference.dereference(ownerComponentRef);
     if (ownerComponent != null) {
       project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(ownerComponent));
     }
     else {
       project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext());
     }
-    setupSdkPaths(sdk, project, ownerComponent);
+
+    DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, new Runnable() {
+      @Override
+      public void run() {
+        setupSdkPaths(sdk, project, ownerComponent);
+      }
+    });
   }
 
   @Override
index 8ffdc879bc3b146368d2d7df8554f55a7602902b..65a5a92d0d41d5df73756223ea6691a04f506eaa 100644 (file)
@@ -22,6 +22,7 @@ import com.intellij.codeInsight.completion.PrioritizedLookupElement;
 import com.intellij.codeInsight.completion.XmlTagInsertHandler;
 import com.intellij.codeInsight.lookup.LookupElement;
 import com.intellij.codeInsight.lookup.LookupElementBuilder;
+import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.RangeMarker;
@@ -51,6 +52,9 @@ import org.jetbrains.annotations.NotNull;
 import java.util.*;
 
 public class DefaultXmlTagNameProvider implements XmlTagNameProvider {
+
+  private static final Logger LOG = Logger.getInstance(DefaultXmlTagNameProvider.class);
+
   @Override
   public void addTagNameVariants(List<LookupElement> elements, @NotNull XmlTag tag, String prefix) {
     final List<String> namespaces;
@@ -81,6 +85,7 @@ public class DefaultXmlTagNameProvider implements XmlTagNameProvider {
       }
 
       PsiElement declaration = descriptor.getDeclaration();
+      LOG.assertTrue(declaration == null || declaration.isValid(), declaration + " contains invalid declaration");
       LookupElementBuilder lookupElement = declaration == null ? LookupElementBuilder.create(qname) : LookupElementBuilder.create(declaration, qname);
       final int separator = qname.indexOf(':');
       if (separator > 0) {
index 9c275e8566340872068c9643de21b5a300c7e6c1..3c3cb45c73213725b5093fdcbd08b1c400e92fb2 100644 (file)
@@ -15,7 +15,8 @@
  */
 package com.intellij.xml.actions;
 
-import com.intellij.codeInsight.actions.SimpleCodeInsightAction;
+import com.intellij.codeInsight.CodeInsightActionHandler;
+import com.intellij.codeInsight.actions.BaseCodeInsightAction;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.Editor;
@@ -29,6 +30,7 @@ import com.intellij.psi.search.PsiElementProcessor;
 import com.intellij.psi.tree.IElementType;
 import com.intellij.psi.util.CachedValueProvider;
 import com.intellij.psi.util.ParameterizedCachedValueProvider;
+import com.intellij.psi.xml.XmlAttribute;
 import com.intellij.psi.xml.XmlEntityDecl;
 import com.intellij.psi.xml.XmlFile;
 import com.intellij.psi.xml.XmlTokenType;
@@ -42,7 +44,7 @@ import org.jetbrains.annotations.Nullable;
 /**
  * @author Dennis.Ushakov
  */
-public class EscapeEntitiesAction extends SimpleCodeInsightAction {
+public class EscapeEntitiesAction extends BaseCodeInsightAction implements CodeInsightActionHandler {
   private static ParameterizedCachedValueImpl<IntObjectHashMap<String>, PsiFile> ESCAPES = new ParameterizedCachedValueImpl<IntObjectHashMap<String>, PsiFile>(
     new ParameterizedCachedValueProvider<IntObjectHashMap<String>, PsiFile>() {
       @Nullable
@@ -95,7 +97,10 @@ public class EscapeEntitiesAction extends SimpleCodeInsightAction {
   private static boolean isCharacterElement(PsiElement element) {
     final IElementType type = element.getNode().getElementType();
     if (type == XmlTokenType.XML_DATA_CHARACTERS) return true;
-    if (type == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN) return true;
+    if (type == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN) {
+      if (element.getParent().getParent() instanceof XmlAttribute) return true;
+    }
+    if (type == XmlTokenType.XML_BAD_CHARACTER) return true;
     if (type == XmlTokenType.XML_START_TAG_START) {
       if (element.getNextSibling() instanceof PsiErrorElement) return true;
       if (element.getParent() instanceof PsiErrorElement) return true;
@@ -108,6 +113,12 @@ public class EscapeEntitiesAction extends SimpleCodeInsightAction {
     return ApplicationManager.getApplication().isInternal() && file instanceof XmlFile;
   }
 
+  @NotNull
+  @Override
+  protected CodeInsightActionHandler getHandler() {
+    return this;
+  }
+
   @Override
   public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
     int[] starts = editor.getSelectionModel().getBlockSelectionStarts();
@@ -128,4 +139,9 @@ public class EscapeEntitiesAction extends SimpleCodeInsightAction {
       }
     }
   }
+
+  @Override
+  public boolean startInWriteAction() {
+    return true;
+  }
 }
index 0a39a86a38d23bf135bf46ea797eccd556298d6e..729de632f0d0f124b9c637a9527a8dd935f717a9 100644 (file)
@@ -668,6 +668,10 @@ public class XmlParsingTest extends ParsingTestCase {
     doTestXml("<!DOCTYPE x3 [<!NOTATION data-sources SYSTEM \"x3\">]>");
   }
 
+  public void testWhitespaceBeforeName() throws Exception {
+    doTestXml("<a>< a</a>");
+  }
+
   public void testCustomMimeType() throws Exception {
     final Language language = new MyLanguage();
     addExplicitExtension(LanguageHtmlScriptContentProvider.INSTANCE, language, new HtmlScriptContentProvider() {
index e6691070ec8de349a2c86536fee6b03e505a1dec..30b991a438e19ed7e05f9473488f6c41ec96bedb 100644 (file)
@@ -54,6 +54,33 @@ public class EscapeEntitiesActionTest extends LightCodeInsightFixtureTestCase {
     doTest("<<<", "xml", "&lt;&lt;&lt;");
   }
 
+  public void testDoctypeSystemPublic() {
+    doTest("<!DOCTYPE html\n" +
+           "        PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" +
+           "        \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">", "html",
+           "<!DOCTYPE html\n" +
+           "        PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" +
+           "        \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
+  }
+
+  public void testXmlAmp() {
+    doTest("<component>\n" +
+           "  amp  &    U+0026 (38) XML 1.0 ampersand\n" +
+           "</component>", "xml",
+           "<component>\n" +
+           "  amp  &amp;    U+0026 (38) XML 1.0 ampersand\n" +
+           "</component>");
+  }
+
+  public void testXmlLt() {
+    doTest("<component>\n" +
+           "  lt   <    U+003C (60) XML 1.0 less-than sign\n" +
+           "</component>", "xml",
+           "<component>\n" +
+           "  lt   &lt;    U+003C (60) XML 1.0 less-than sign\n" +
+           "</component>");
+  }
+
   public void testMultiCaret() {
     doTest("<a><selection><</selection></a>\n" +
            "<a><selection><</selection></a>\n" +
diff --git a/xml/tests/testData/psi/testWhitespaceBeforeName.txt b/xml/tests/testData/psi/testWhitespaceBeforeName.txt
new file mode 100644 (file)
index 0000000..84788d2
--- /dev/null
@@ -0,0 +1,20 @@
+XmlFile:test.xml
+  PsiElement(XML_DOCUMENT)
+    PsiElement(XML_PROLOG)
+      <empty list>
+    XmlTag:a
+      XmlToken:XML_START_TAG_START('<')
+      XmlToken:XML_NAME('a')
+      XmlToken:XML_TAG_END('>')
+      XmlTag:
+        XmlToken:XML_START_TAG_START('<')
+        PsiErrorElement:Tag name expected
+          <empty list>
+        PsiWhiteSpace(' ')
+        PsiElement(XML_ATTRIBUTE)
+          XmlToken:XML_NAME('a')
+          PsiErrorElement:'=' expected
+            <empty list>
+      XmlToken:XML_END_TAG_START('</')
+      XmlToken:XML_NAME('a')
+      XmlToken:XML_TAG_END('>')
\ No newline at end of file
index a98e5dbc5c536f9d773758772141665e794fbe43..189fb80f2fea77dff61a9709c2eac1425eb77a9f 100644 (file)
@@ -17,6 +17,7 @@ package com.intellij.psi.impl.source.parsing.xml;
 
 import com.intellij.codeInsight.daemon.XmlErrorMessages;
 import com.intellij.lang.PsiBuilder;
+import com.intellij.psi.TokenType;
 import com.intellij.psi.tree.CustomParsingType;
 import com.intellij.psi.tree.IElementType;
 import com.intellij.psi.tree.ILazyParseableElementType;
@@ -177,7 +178,7 @@ public class XmlParsing {
     }
 
     final String tagName;
-    if (token() != XML_NAME) {
+    if (token() != XML_NAME || myBuilder.rawLookup(-1) == TokenType.WHITE_SPACE) {
       error(XmlErrorMessages.message("xml.parsing.tag.name.expected"));
       tagName = "";
     }