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