package jetbrains.mps.ide.platform.watching; /*Generated by MPS */ import com.intellij.openapi.components.ApplicationComponent; import org.apache.log4j.Logger; import org.apache.log4j.LogManager; import com.intellij.openapi.project.ProjectManager; import jetbrains.mps.make.IMakeNotificationListener; import java.util.List; import jetbrains.mps.internal.collections.runtime.ListSequence; import java.util.ArrayList; import com.intellij.util.ui.update.MergingUpdateQueue; import java.util.concurrent.atomic.AtomicInteger; import jetbrains.mps.make.IMakeService; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.apache.log4j.Level; import jetbrains.mps.util.Computable; import jetbrains.mps.smodel.ModelAccess; import com.intellij.openapi.application.ApplicationManager; import jetbrains.mps.smodel.MPSModuleRepository; import jetbrains.mps.progress.EmptyProgressMonitor; import com.intellij.util.ui.update.Update; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.progress.ProgressIndicator; import org.jetbrains.mps.openapi.util.ProgressMonitor; import jetbrains.mps.progress.ProgressMonitorAdapter; import jetbrains.mps.make.MakeNotification; public class ReloadManagerComponent extends ReloadManager implements ApplicationComponent { private static final Logger LOG = LogManager.getLogger(ReloadManagerComponent.class); private final ProjectManager myProjectManager; private final IMakeNotificationListener myMakeListener = new ReloadManagerComponent.NotReloadingOnMakeListener(); private final List<ReloadListener> myReloadListeners = ListSequence.fromList(new ArrayList<ReloadListener>()); private final ReloadManagerComponent.ReloadSessionBroker myReloadSessionBroker = new ReloadManagerComponent.ReloadSessionBroker(); private final MergingUpdateQueue myTaskQueue = new MergingUpdateQueue("Reload Manager Queue", 500, true, null, null, null, true); private final Object myUpdateId = new Object(); private final AtomicInteger mySuspendCount = new AtomicInteger(0); private final IMakeService myMakeService; public ReloadManagerComponent(ProjectManager projectManager, IMakeService makeService) { myProjectManager = projectManager; myTaskQueue.setRestartTimerOnAdd(true); myMakeService = makeService; } public void initComponent() { myMakeService.addListener(myMakeListener); } public void disposeComponent() { myMakeService.removeListener(myMakeListener); } @NonNls @NotNull public String getComponentName() { return "Reload Manager"; } public void suspendReloads() { int count = mySuspendCount.incrementAndGet(); assert count >= 0; myTaskQueue.suspend(); } public void resumeReloads() { int count = mySuspendCount.decrementAndGet(); assert count >= 0; if (count == 0) { myTaskQueue.resume(); } } @Override public <T extends ReloadParticipant> void runReload(Class<T> participantClass, ReloadAction<T> reloadAction) { ReloadSession rs; rs = myReloadSessionBroker.employ(); try { T participant = rs.getParticipant(participantClass); reloadAction.runAction(participant); rs.updateStatus(); } catch (RuntimeException e) { if (LOG.isEnabledFor(Level.ERROR)) { LOG.error("Exception during reload", e); } throw e; } finally { myReloadSessionBroker.dismiss(rs); } queueReloadSession(); } @Override public void addReloadListener(ReloadListener listener) { ListSequence.fromList(myReloadListeners).addElement(listener); } @Override public void removeReloadListener(ReloadListener listener) { ListSequence.fromList(myReloadListeners).removeElement(listener); } @Override public <T> T computeNoReload(Computable<T> computable) { try { suspendReloads(); return computable.compute(); } finally { resumeReloads(); } } @Override public void flush() { // synchronously commit all pending reload requests ReloadSession session = myReloadSessionBroker.waitForUnemployed(); if (session == null) { return; } // see MPS-18743, 21760 ModelAccess.instance().runWriteAction(new Runnable() { public void run() { assert ApplicationManager.getApplication().isWriteAccessAllowed() : "Platform write access not allowed: execute from EDT or under progress"; MPSModuleRepository.getInstance().saveAll(); } }); // Q: also do normal progressMonitor, as in real reload on timeout ? session.doReload(new EmptyProgressMonitor()); } private void queueReloadSession() { if (!(myReloadSessionBroker.hasUnemployed())) { return; } myTaskQueue.queue(new Update(myUpdateId) { public void run() { for (Project project : myProjectManager.getOpenProjects()) { if (project.getComponent(ProjectLevelVcsManager.class).isBackgroundVcsOperationRunning()) { queueReloadSession(); return; } } final ReloadSession rs = myReloadSessionBroker.getUnemployed(); if (rs == null) { queueReloadSession(); return; } if (rs.isEmpty()) { return; } ApplicationManager.getApplication().invokeAndWait(new Runnable() { public void run() { // see MPS-18743, 21760 // fixme the problem itself must be fixed (reloading module while there are changed models in it ModelAccess.instance().runWriteAction(new Runnable() { public void run() { MPSModuleRepository.getInstance().saveAll(); } }); } }, ModalityState.NON_MODAL); ProgressManager.getInstance().run(new Task.Backgroundable(null, "Reloading Files", false) { @Override public void run(@NotNull final ProgressIndicator progressIndicator) { ProgressMonitor monitor = new ProgressMonitorAdapter(progressIndicator); monitor.start("Reloading Files", 1); try { monitor.step("Reloading File System"); rs.doReload(monitor.subTask(1)); } finally { monitor.done(); } } }); } }); } private class ReloadSessionBroker { private ReloadSession myReloadSession; /*package*/ synchronized ReloadSession employ() { if (myReloadSession == null) { myReloadSession = new ReloadSession(myReloadListeners); } myReloadSession.incEmployCount(); return myReloadSession; } /*package*/ synchronized void dismiss(ReloadSession rs) { assert myReloadSession == rs; rs.decEmployCount(); notify(); } /*package*/ boolean hasUnemployed() { ReloadSession rs = myReloadSession; return rs != null && !(rs.isBeingEmployed()); } /*package*/ synchronized ReloadSession getUnemployed() { if (myReloadSession == null || myReloadSession.isBeingEmployed()) { return null; } ReloadSession rs = myReloadSession; myReloadSession = null; return rs; } /*package*/ synchronized ReloadSession waitForUnemployed() { if (myReloadSession == null) { return null; } while (myReloadSession != null && myReloadSession.isBeingEmployed()) { try { wait(); } catch (InterruptedException e) { throw new RuntimeException("Waiting for reload session to be freed failed", e); } } ReloadSession rs = myReloadSession; myReloadSession = null; return rs; } } private class NotReloadingOnMakeListener extends IMakeNotificationListener.Stub { @Override public void sessionOpened(MakeNotification notification) { suspendReloads(); } @Override public void sessionClosed(MakeNotification notification) { resumeReloads(); } } }