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();
}
}
}
}