package jetbrains.mps.ide.migration; /*Generated by MPS */ import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.openapi.components.StoragePathMacros; import com.intellij.openapi.components.AbstractProjectComponent; import com.intellij.openapi.components.PersistentStateComponent; import jetbrains.mps.ide.migration.wizard.MigrationSession; import jetbrains.mps.classloading.ClassLoaderManager; import jetbrains.mps.migration.global.MigrationOptions; import jetbrains.mps.project.MPSProject; import jetbrains.mps.migration.global.ProjectMigrationProperties; import jetbrains.mps.ide.migration.wizard.MigrationErrorDescriptor; import com.intellij.openapi.project.Project; import com.intellij.openapi.application.ApplicationManager; import jetbrains.mps.ide.MPSCoreComponents; import java.util.concurrent.atomic.AtomicInteger; import jetbrains.mps.RuntimeFlags; import com.intellij.openapi.startup.StartupManager; import jetbrains.mps.progress.EmptyProgressMonitor; import jetbrains.mps.ide.migration.wizard.MigrationWizard; import com.intellij.openapi.project.ex.ProjectManagerEx; import jetbrains.mps.baseLanguage.closures.runtime.Wrappers; import java.util.List; import jetbrains.mps.lang.migration.runtime.base.Problem; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import org.jetbrains.annotations.NotNull; import com.intellij.openapi.progress.ProgressIndicator; import jetbrains.mps.internal.collections.runtime.Sequence; import jetbrains.mps.ide.migration.check.MigrationOutputUtil; import com.intellij.openapi.application.ModalityState; import com.intellij.ide.GeneralSettings; import jetbrains.mps.smodel.RepoListenerRegistrar; import jetbrains.mps.ide.platform.watching.ReloadManager; import org.jetbrains.annotations.NonNls; import jetbrains.mps.migration.component.util.MigrationsUtil; import org.jetbrains.mps.openapi.module.SModule; import java.util.Set; import jetbrains.mps.internal.collections.runtime.SetSequence; import java.util.HashSet; import jetbrains.mps.smodel.Language; import org.jetbrains.mps.openapi.language.SLanguage; import jetbrains.mps.internal.collections.runtime.ISelector; import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory; import jetbrains.mps.internal.collections.runtime.IVisitor; import jetbrains.mps.internal.collections.runtime.ListSequence; import org.jetbrains.mps.openapi.util.ProgressMonitor; import jetbrains.mps.progress.ProgressMonitorAdapter; import com.intellij.util.WaitForProgressToShow; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.openapi.application.Application; import jetbrains.mps.ide.vfs.VirtualFileUtils; import jetbrains.mps.ide.platform.watching.ReloadListener; import org.jetbrains.mps.openapi.module.SRepositoryContentAdapter; import jetbrains.mps.internal.collections.runtime.IWhereFilter; import jetbrains.mps.smodel.event.SModelEventVisitor; import jetbrains.mps.smodel.event.SModelEventVisitorAdapter; import jetbrains.mps.smodel.event.SModelLanguageEvent; import jetbrains.mps.smodel.event.SModelDevKitEvent; import jetbrains.mps.smodel.ModelsEventsCollector; import jetbrains.mps.smodel.event.SModelEvent; import org.jetbrains.mps.openapi.model.SModel; import jetbrains.mps.classloading.MPSClassesListenerAdapter; import jetbrains.mps.module.ReloadableModuleBase; import org.jetbrains.annotations.Nullable; /** * At the first startup, migration is not required * The need for migration is determined after startup by checking all modules once and then watching the repo * Whether some change requires migration to be executed, the user is notified about that and the project is reloaded * with myState.migrationRequired set to true. * In this case, the migration is executed and no watchers are added (as they could try to run the migration once again) * After the migration is completed, myState.migrationRequired is set to false again and the project is reloaded * * Reasons to reload project after migration: * 1. The reload cycle with migration wizard happens w/o adding repo listeners * 2. Models should be unloaded after migration */ @State(name = "MigrationTrigger", storages = @Storage(value = StoragePathMacros.WORKSPACE_FILE) ) public class MigrationTrigger extends AbstractProjectComponent implements PersistentStateComponent<MigrationTrigger.MyState>, IStartupMigrationExecutor, MigrationSession { private final ClassLoaderManager myClassLoaderManager; private MigrationOptions myOptions = new MigrationOptions(); private MPSProject myMpsProject; private final MigrationManager myMigrationManager; private MigrationTrigger.MyState myState = new MigrationTrigger.MyState(); private boolean myMigrationQueued = false; private ProjectMigrationProperties myProperties; private MigrationTrigger.MyRepoListener myRepoListener = new MigrationTrigger.MyRepoListener(); private MigrationTrigger.MyReloadListener myReloadListener = new MigrationTrigger.MyReloadListener(); private MigrationTrigger.MyClassesListener myClassesListener = new MigrationTrigger.MyClassesListener(); private MigrationTrigger.MyPropertiesListener myPropertiesListener = new MigrationTrigger.MyPropertiesListener(); private boolean myListenersAdded = false; private MigrationErrorDescriptor myErrors = null; public MigrationTrigger(Project ideaProject, MPSProject p, MigrationManager migrationManager, ProjectMigrationProperties props) { super(ideaProject); myMpsProject = p; myMigrationManager = migrationManager; myProperties = props; myClassLoaderManager = ApplicationManager.getApplication().getComponent(MPSCoreComponents.class).getClassLoaderManager(); } private final AtomicInteger myBlocked = new AtomicInteger(0); public void blockMigrationsCheck() { myBlocked.incrementAndGet(); } public void unblockMigrationsCheck() { int locks = myBlocked.decrementAndGet(); assert locks >= 0 : "Non-paired block-unblock method usage"; if (locks == 0) { checkMigrationNeeded(); } } public void projectOpened() { // this is a hack for migration task purposes if (RuntimeFlags.getTestMode().isInsideTestEnvironment()) { return; } if (!(myState.migrationRequired)) { addListeners(); checkMigrationNeeded(); } else { saveAndSetTipsState(); StartupManager.getInstance(myProject).registerPostStartupActivity(new Runnable() { public void run() { ApplicationManager.getApplication().runWriteAction(new Runnable() { public void run() { syncRefresh(new EmptyProgressMonitor()); } }); ApplicationManager.getApplication().invokeLater(new Runnable() { public void run() { myState.migrationRequired = false; final MigrationWizard wizard = new MigrationWizard(myProject, MigrationTrigger.this); // final reload is needed to cleanup memory (unload models) and do possible switches (e.g. to a new persistence) boolean finished = wizard.showAndGet(); restoreTipsState(); if (!(finished) && myErrors == null) { return; } if (myErrors == null) { ApplicationManager.getApplication().runWriteAction(new Runnable() { public void run() { ProjectManagerEx.getInstance().reloadProject(myProject); } }); } else { if (myErrors == null) { return; } StartupManager.getInstance(myProject).runWhenProjectIsInitialized(new Runnable() { public void run() { final Wrappers._T<List<Problem>> problems = new Wrappers._T<List<Problem>>(); ProgressManager.getInstance().run(new Task.Modal(myProject, "Collecting Errors", false) { public void run(@NotNull final ProgressIndicator progressIndicator) { myMpsProject.getRepository().getModelAccess().runReadAction(new Runnable() { public void run() { problems.value = Sequence.fromIterable(myErrors.getProblems(progressIndicator)).toListSequence(); } }); } }); ApplicationManager.getApplication().invokeLater(new Runnable() { public void run() { myMpsProject.getRepository().getModelAccess().runReadAction(new Runnable() { public void run() { MigrationOutputUtil.showProblems(myProject, problems.value); } }); } }, ModalityState.NON_MODAL); } }); } } }); } }); } } public void projectClosed() { removeListeners(); } private void saveAndSetTipsState() { if (myState.tips == null) { myState.tips = GeneralSettings.getInstance().isShowTipsOnStartup(); } GeneralSettings.getInstance().setShowTipsOnStartup(false); } private void restoreTipsState() { if (myState.tips == null) { return; } GeneralSettings.getInstance().setShowTipsOnStartup(myState.tips); myState.tips = null; } private void addListeners() { myListenersAdded = true; new RepoListenerRegistrar(myMpsProject.getRepository(), myRepoListener).attach(); myClassLoaderManager.addClassesHandler(this.myClassesListener); myProperties.addListener(myPropertiesListener); ReloadManager.getInstance().addReloadListener(myReloadListener); } private boolean removeListeners() { if (!(myListenersAdded)) { return true; } myProperties.removeListener(myPropertiesListener); myClassLoaderManager.removeClassesHandler(myClassesListener); new RepoListenerRegistrar(myMpsProject.getRepository(), myRepoListener).detach(); ReloadManager.getInstance().removeReloadListener(myReloadListener); return false; } @NonNls @NotNull public String getComponentName() { return "MigrationTrigger"; } public synchronized void resetMigrationQueuedFlag() { myMigrationQueued = false; } /*package*/ void checkMigrationNeeded() { myMpsProject.getRepository().getModelAccess().runWriteAction(new Runnable() { public void run() { postponeMigrationIfNeededOnModuleChange(MigrationsUtil.getMigrateableModulesFromProject(myMpsProject)); } }); } private synchronized void postponeMigrationIfNeededOnModuleChange(Iterable<SModule> modules) { if (!(myMigrationQueued)) { Set<SModule> modules2Check = SetSequence.fromSetWithValues(new HashSet<SModule>(), modules); if (myMigrationManager.importVersionsUpdateRequired(modules2Check) || myMigrationManager.isMigrationRequired(modules2Check)) { postponeMigration(); } } } private synchronized void postponeMigrationIfNeededOnLanguageReload(Iterable<Language> languages) { if (myMigrationQueued) { return; } // if a new language is added to a repo, all modules in project using it // should be checked for whether their migration is needed final Set<SModule> modules2Check = SetSequence.fromSet(new HashSet<SModule>()); final List<SLanguage> addedLanguages = Sequence.fromIterable(languages).select(new ISelector<Language, SLanguage>() { public SLanguage select(Language it) { return MetaAdapterFactory.getLanguage(it.getModuleReference()); } }).toListSequence(); Sequence.fromIterable(MigrationsUtil.getMigrateableModulesFromProject(myMpsProject)).visitAll(new IVisitor<SModule>() { public void visit(SModule it) { Set<SLanguage> used = new HashSet<SLanguage>(it.getUsedLanguages()); used.retainAll(addedLanguages); if (!(used.isEmpty())) { SetSequence.fromSet(modules2Check).addElement(it); } } }); if (myMigrationManager.importVersionsUpdateRequired(modules2Check) || ListSequence.fromList(myMigrationManager.getModuleMigrationsToApply(modules2Check)).isNotEmpty()) { postponeMigration(); } } public synchronized void postponeMigration() { if (myBlocked.get() != 0) { return; } final Project ideaProject = myProject; final Iterable<SModule> allModules = MigrationsUtil.getMigrateableModulesFromProject(myMpsProject); saveAndSetTipsState(); myMigrationQueued = true; // wait until project is fully loaded (if not yet) StartupManager.getInstance(ideaProject).runWhenProjectIsInitialized(new Runnable() { public void run() { // as we use ui, postpone to EDT ApplicationManager.getApplication().invokeLater(new Runnable() { public void run() { restoreTipsState(); final Wrappers._boolean importVersionsUpdateRequired = new Wrappers._boolean(); final Wrappers._boolean migrationRequired = new Wrappers._boolean(); myMpsProject.getRepository().getModelAccess().runReadAction(new Runnable() { public void run() { importVersionsUpdateRequired.value = myMigrationManager.importVersionsUpdateRequired(allModules); migrationRequired.value = myMigrationManager.isMigrationRequired(); } }); boolean resave; boolean migrate; if (migrationRequired.value) { migrate = MigrationDialogUtil.showMigrationConfirmation(myMpsProject, allModules, myMigrationManager); resave = importVersionsUpdateRequired.value && migrate; } else { migrate = false; resave = MigrationDialogUtil.showResaveConfirmation(myMpsProject); } if (resave) { ProgressManager.getInstance().run(new Task.Modal(ideaProject, "Resaving Module Descriptors", false) { public void run(@NotNull ProgressIndicator progressIndicator) { ProgressMonitor progressMonitor = new ProgressMonitorAdapter(progressIndicator); progressMonitor.start("Resaving module descriptors", 10); ProgressMonitor refreshing = progressMonitor.subTask(9); syncRefresh(refreshing); ProgressMonitor saving = progressMonitor.subTask(1); saving.start("Saving...", Sequence.fromIterable(allModules).count()); for (final SModule module : Sequence.fromIterable(allModules)) { saving.advance(1); WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(new Runnable() { public void run() { myMpsProject.getRepository().getModelAccess().runWriteAction(new Runnable() { public void run() { myMigrationManager.doUpdateImportVersions(module); } }); } }); } } }); } if (migrate) { // set flag to execute migration after startup // NOTE we need to set it here as in invokeLater it can // be executed when save session already passed, see MPS-22045 myState.migrationRequired = true; syncRefresh(new EmptyProgressMonitor()); if (!(myMigrationManager.isMigrationRequired())) { MigrationDialogUtil.showNoMigrationMessage(myProject); return; } VirtualFileManager.getInstance().asyncRefresh(new Runnable() { public void run() { final Application application = ApplicationManager.getApplication(); application.invokeLater(new Runnable() { public void run() { application.getComponent(ReloadManager.class).flush(); // reload project and start migration assist ProjectManagerEx.getInstance().reloadProject(ideaProject); } }); } }); } else if (resave) { // if project is to be reloaded, not resetting migrationQueued flag until reload resetMigrationQueuedFlag(); } } }); } }); } private void syncRefresh(ProgressMonitor progressMonitor) { final Application application = ApplicationManager.getApplication(); WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(new Runnable() { public void run() { application.saveAll(); } }); VirtualFileUtils.refreshSynchronouslyRecursively(myProject.getBaseDir(), progressMonitor.subTask(1)); WaitForProgressToShow.runOrInvokeAndWaitAboveProgress(new Runnable() { public void run() { application.getComponent(ReloadManager.class).flush(); } }); } private class MyReloadListener implements ReloadListener { private boolean myUnderReload = false; @Override public void reloadStarted() { myUnderReload = true; } @Override public void reloadFinished() { myUnderReload = false; } public boolean isIsUnderReload() { return myUnderReload; } } private boolean isProjectMigrateableModule(@NotNull SModule module) { return myMpsProject.getProjectModulesWithGenerators().contains(module) && MigrationsUtil.isModuleMigrateable(module); } private class MyRepoListener extends SRepositoryContentAdapter { private class ModuleBatchUpdater implements Runnable { public Set<SModule> modulesTouched = SetSequence.fromSet(new HashSet<SModule>()); private boolean touchedUnderReload = false; public void run() { myTask = null; List<SModule> toUpdate = SetSequence.fromSet(modulesTouched).distinct().where(new IWhereFilter<SModule>() { public boolean accept(SModule it) { return isProjectMigrateableModule(it); } }).toListSequence(); if (!(touchedUnderReload)) { for (SModule m : ListSequence.fromList(toUpdate)) { updateSingleModuleDescriptorSilently(m); } } postponeMigrationIfNeededOnModuleChange(toUpdate); } } private MigrationTrigger.MyRepoListener.ModuleBatchUpdater myTask = null; private void updateSingleModuleDescriptorSilently(SModule module) { if (!(isProjectMigrateableModule(module))) { return; } myMigrationManager.doUpdateImportVersions(module); } private void triggerOnModuleChanged(SModule module) { if (myTask == null) { myTask = new MigrationTrigger.MyRepoListener.ModuleBatchUpdater(); ApplicationManager.getApplication().invokeLater(new Runnable() { public void run() { myMpsProject.getModelAccess().executeCommand(myTask); } }); } SetSequence.fromSet(myTask.modulesTouched).addElement(module); if (myReloadListener.isIsUnderReload()) { myTask.touchedUnderReload = true; } } private SModelEventVisitor myVisitor = new SModelEventVisitorAdapter() { @Override public void visitLanguageEvent(SModelLanguageEvent event) { updateSingleModuleDescriptorSilently(event.getModel().getModule()); } @Override public void visitDevKitEvent(SModelDevKitEvent event) { updateSingleModuleDescriptorSilently(event.getModel().getModule()); } }; private ModelsEventsCollector myModelListener = new ModelsEventsCollector() { @Override protected void eventsHappened(List<SModelEvent> events) { ListSequence.fromList(events).visitAll(new IVisitor<SModelEvent>() { public void visit(SModelEvent it) { it.accept(myVisitor); } }); } }; @Override public void moduleAdded(@NotNull SModule module) { super.moduleAdded(module); triggerOnModuleChanged(module); } @Override public void moduleChanged(@NotNull SModule module) { super.moduleChanged(module); triggerOnModuleChanged(module); } @Override protected void startListening(SModel model) { super.startListening(model); if (isProjectMigrateableModule(model.getModule())) { myModelListener.startListeningToModel(model); } } @Override protected void stopListening(SModel model) { super.stopListening(model); if (isProjectMigrateableModule(model.getModule())) { myModelListener.stopListeningToModel(model); } } } private class MyClassesListener extends MPSClassesListenerAdapter { @Override public void afterClassesLoaded(final Set<? extends ReloadableModuleBase> modules) { ApplicationManager.getApplication().invokeLater(new Runnable() { public void run() { myMpsProject.getRepository().getModelAccess().runWriteAction(new Runnable() { public void run() { postponeMigrationIfNeededOnLanguageReload(SetSequence.fromSet(modules).ofType(Language.class)); } }); } }); } } private class MyPropertiesListener implements ProjectMigrationProperties.MigrationPropertiesReloadListener { @Override public void onReload() { checkMigrationNeeded(); } } public MigrationErrorDescriptor getErrorDescriptor() { return myErrors; } public void setErrorDescriptor(MigrationErrorDescriptor errors) { myErrors = errors; } @Nullable @Override public MigrationTrigger.MyState getState() { return myState; } @Override public void loadState(MigrationTrigger.MyState state) { myState = state; } public static class MyState { public boolean migrationRequired = false; public Boolean tips; } @Override public jetbrains.mps.project.Project getProject() { return myMpsProject; } @Override public MigrationManager getMigrationManager() { return myMigrationManager; } @Override public MigrationOptions getOptions() { return myOptions; } }