package jetbrains.mps.tool.environment; /*Generated by MPS */ import org.apache.log4j.Logger; import org.apache.log4j.LogManager; import com.intellij.idea.IdeaTestApplication; import org.jetbrains.annotations.NotNull; import jetbrains.mps.ide.platform.watching.FSChangesWatcher; import jetbrains.mps.ide.MPSCoreComponents; import jetbrains.mps.RuntimeFlags; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.project.ProjectManagerAdapter; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess; import com.intellij.openapi.application.ApplicationManager; import java.io.File; import java.util.List; import java.util.ArrayList; import jetbrains.mps.project.MPSProject; import com.intellij.openapi.application.ModalityState; import jetbrains.mps.util.FileUtil; import com.intellij.openapi.project.ex.ProjectManagerEx; import jetbrains.mps.util.Reference; import com.intellij.ide.startup.StartupManagerEx; import jetbrains.mps.vfs.CachingFileSystem; import jetbrains.mps.ide.vfs.IdeaFSComponent; import jetbrains.mps.vfs.DefaultCachingContext; import com.intellij.testFramework.PlatformTestUtil; import jetbrains.mps.smodel.ModelAccess; import jetbrains.mps.core.platform.Platform; import org.jetbrains.annotations.Nullable; import java.util.concurrent.Semaphore; import com.intellij.openapi.startup.StartupManager; /** * Use #getOrCreate method to construct this kind of environment * TODO: fix dispose methods */ public class IdeaEnvironment extends EnvironmentBase { private static final Logger LOG = LogManager.getLogger(IdeaEnvironment.class); private IdeaTestApplication myIdeaApplication; static { EnvironmentBase.initializeLog4j(); } protected IdeaEnvironment(@NotNull EnvironmentConfig config) { super(config); } /** * creates a new IdeaEnvironment or returns the cached one */ @NotNull public static Environment getOrCreate(@NotNull EnvironmentConfig config) { Environment currentEnv = EnvironmentContainer.get(); if (currentEnv != null) { if (!(currentEnv instanceof IdeaEnvironment)) { throw new IllegalStateException("Still no support for interchanging lightweight and heavyweight environments"); } currentEnv.retain(); return currentEnv; } else { IdeaEnvironment ideaEnv = new IdeaEnvironment(config); ideaEnv.init(); assert EnvironmentContainer.get() == ideaEnv; return ideaEnv; } } @Override public void init() { if (LOG.isInfoEnabled()) { LOG.info("Creating IDEA environment"); } EnvironmentBase.setSystemProperties(true); EnvironmentBase.setIdeaPluginsToLoad(myConfig); myIdeaApplication = createIdeaTestApp(); // Necessary to listen for FS changes notifications & notify MPS FS listeners to update repository/.. // this code will work if on executing tests with "reuse caches" option // TODO: should we modify FSChangesWatcher to always listen for FS notifications (even in tests)? FSChangesWatcher.instance().initComponent(true); disallowAccessToClosedProjectsDir(); MPSCoreComponents coreComponents = getMPSCoreComponents(); super.init(coreComponents.getLibraryInitializer()); } private void disallowAccessToClosedProjectsDir() { if (RuntimeFlags.isTestMode()) { ProjectManager.getInstance().addProjectManagerListener(new ProjectManagerAdapter() { @Override public void projectClosed(Project project) { VfsRootAccess.disallowRootAccess(project.getBasePath()); } }); } } private MPSCoreComponents getMPSCoreComponents() { return ApplicationManager.getApplication().getComponent(MPSCoreComponents.class); } private IdeaTestApplication createIdeaTestApp() { if (LOG.isInfoEnabled()) { LOG.info("Creating IdeaTestApplication"); } return IdeaTestApplication.getInstance(); } @Override @NotNull public jetbrains.mps.project.Project doOpenProject(@NotNull File projectFile) { if (RuntimeFlags.isTestMode()) { VfsRootAccess.allowRootAccess(projectFile.getAbsolutePath()); } return openProjectInIdeaEnvironment(projectFile); } @NotNull @Override public jetbrains.mps.project.Project createEmptyProject() { checkInitialized(); if (LOG.isInfoEnabled()) { LOG.info("Creating an empty project"); } File dummyProjectFile = createDummyProjectFile(); jetbrains.mps.project.Project dummyProject = openProject(dummyProjectFile); return dummyProject; } @Override public void doDispose() { ApplicationManager.getApplication().invokeAndWait(new Runnable() { @Override public void run() { List<jetbrains.mps.project.Project> openedProjects = new ArrayList<jetbrains.mps.project.Project>(jetbrains.mps.project.ProjectManager.getInstance().getOpenedProjects()); for (final jetbrains.mps.project.Project project : openedProjects) { if (project instanceof MPSProject) { // MPSProject need to be disposed outside writeAction to prevent exception: // java.lang.IllegalStateException: Must not call closeProject() from under write action // because fireProjectClosing() listeners must have a chance to do something useful // TODO: find way to put MPSProject#dispose() under writeAction project.dispose(); } else { ApplicationManager.getApplication().runWriteAction(new Runnable() { public void run() { project.dispose(); } }); } } ApplicationManager.getApplication().runWriteAction(new Runnable() { public void run() { myIdeaApplication.dispose(); } }); } }, ModalityState.NON_MODAL); } private File createDummyProjectFile() { File dummyProjDir = FileUtil.createTmpDir(); File dotMps = new File(dummyProjDir, Project.DIRECTORY_STORE_FOLDER); dotMps.mkdir(); dummyProjDir.deleteOnExit(); return dummyProjDir; } @NotNull private jetbrains.mps.project.Project openProjectInIdeaEnvironment(File projectFile) { if (!(projectFile.exists())) { throw new RuntimeException("Can't find project file " + projectFile.getAbsolutePath()); } final ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx(); final String filePath = projectFile.getAbsolutePath(); final IdeaEnvironment.PostStartupActivitiesWaiter waiter = new IdeaEnvironment.PostStartupActivitiesWaiter(); final Reference<Project> project = new Reference<Project>(); final Reference<Exception> exc = new Reference<Exception>(); ApplicationManager.getApplication().invokeAndWait(new Runnable() { public void run() { try { if (LOG.isInfoEnabled()) { LOG.info("Load and open the project with path '" + filePath + "'"); } project.set(projectManager.loadAndOpenProject(filePath)); waiter.init(project.get()); refreshProjectDir(project.get()); } catch (Exception e) { exc.set(e); } } }, ModalityState.NON_MODAL); if (!(exc.isNull())) { throw new RuntimeException("ProjectManager could not load project from " + projectFile.getAbsolutePath(), exc.get()); } // On new (171.4249) platform post startup activities frequently do not pass before waiter.wait0() check, so have to add flushAllEvents() here if (!(StartupManagerEx.getInstanceEx(project.get()).postStartupActivityPassed())) { flushAllEvents(); } waiter.wait0(); return project.get().getComponent(MPSProject.class); } private void refreshProjectDir(@NotNull Project project) { // calling sync refresh for FS in order to update all modules/models loaded from the project // if unit-test is executed with the "reuse caches" option. String basePath = project.getBasePath(); if (basePath != null) { CachingFileSystem fs = ApplicationManager.getApplication().getComponent(IdeaFSComponent.class); fs.getFile(basePath).refresh(new DefaultCachingContext(true, true)); } } @Override public void flushAllEvents() { checkInitialized(); ApplicationManager.getApplication().invokeAndWait(new Runnable() { public void run() { try { PlatformTestUtil.dispatchAllEventsInIdeEventQueue(); } catch (InterruptedException e) { throw new RuntimeException(e); } } }, ModalityState.NON_MODAL); ModelAccess.instance().flushEventQueue(); } @Override public Platform getPlatform() { return getMPSCoreComponents().getPlatform(); } @Nullable @Override protected ClassLoader rootClassLoader() { return null; } private static final class PostStartupActivitiesWaiter { private final Semaphore mySem = new Semaphore(0); private volatile Project myProject; public void init(Project project) { if (project != null) { myProject = project; StartupManager.getInstance(project).registerPostStartupActivity(new Runnable() { public void run() { mySem.release(); } }); } } public void wait0() { if (myProject != null) { try { mySem.acquire(); } catch (InterruptedException e) { throw new RuntimeException("Caught exception while waiting for the post startup activities", e); } assert StartupManagerEx.getInstanceEx(myProject).postStartupActivityPassed(); } } } }