intercept project closing event while compilation is in progress
[fedora-idea.git] / compiler / impl / com / intellij / compiler / progress / CompilerTask.java
blob1ce9271a6dce2ed3fc7e3bd4a273b0eeb4f20d65
1 /*
2 * @author: Eugene Zhuravlev
3 * Date: Jan 22, 2003
4 * Time: 2:25:31 PM
5 */
6 package com.intellij.compiler.progress;
8 import com.intellij.compiler.CompilerManagerImpl;
9 import com.intellij.compiler.CompilerMessageImpl;
10 import com.intellij.compiler.impl.CompileDriver;
11 import com.intellij.compiler.impl.CompilerErrorTreeView;
12 import com.intellij.ide.errorTreeView.NewErrorTreeViewPanel;
13 import com.intellij.ide.errorTreeView.impl.ErrorTreeViewConfiguration;
14 import com.intellij.ide.impl.ProjectUtil;
15 import com.intellij.lang.annotation.HighlightSeverity;
16 import com.intellij.openapi.application.Application;
17 import com.intellij.openapi.application.ApplicationManager;
18 import com.intellij.openapi.application.ModalityState;
19 import com.intellij.openapi.compiler.*;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.editor.Document;
22 import com.intellij.openapi.fileEditor.FileDocumentManager;
23 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
24 import com.intellij.openapi.progress.EmptyProgressIndicator;
25 import com.intellij.openapi.progress.ProgressFunComponentProvider;
26 import com.intellij.openapi.progress.ProgressIndicator;
27 import com.intellij.openapi.progress.Task;
28 import com.intellij.openapi.progress.util.ProgressIndicatorBase;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.project.ProjectManager;
31 import com.intellij.openapi.project.ProjectManagerListener;
32 import com.intellij.openapi.ui.DialogWrapper;
33 import com.intellij.openapi.ui.Messages;
34 import com.intellij.openapi.util.Computable;
35 import com.intellij.openapi.util.Key;
36 import com.intellij.openapi.util.TextRange;
37 import com.intellij.openapi.vfs.VirtualFile;
38 import com.intellij.openapi.wm.ToolWindowId;
39 import com.intellij.openapi.wm.ToolWindowManager;
40 import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
41 import com.intellij.peer.PeerFactory;
42 import com.intellij.pom.Navigatable;
43 import com.intellij.problems.Problem;
44 import com.intellij.problems.WolfTheProblemSolver;
45 import com.intellij.ui.content.*;
46 import com.intellij.util.Alarm;
47 import com.intellij.util.messages.MessageBusConnection;
48 import com.intellij.util.ui.MessageCategory;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
52 import javax.swing.*;
53 import java.awt.*;
54 import java.util.ArrayList;
55 import java.util.StringTokenizer;
56 import java.util.concurrent.Semaphore;
57 import java.util.concurrent.atomic.AtomicBoolean;
59 public class CompilerTask extends Task.Backgroundable {
60 private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.progress.CompilerProgressIndicator");
61 private static final boolean IS_UNIT_TEST_MODE = ApplicationManager.getApplication().isUnitTestMode();
62 private static final int UPDATE_INTERVAL = 50; //msec. 20 frames per second.
63 private static final Key<Key<?>> CONTENT_ID_KEY = Key.create("CONTENT_ID");
64 private final Key<Key<?>> myContentId = Key.create("compile_content");
65 private CompilerProgressDialog myDialog;
66 private NewErrorTreeViewPanel myErrorTreeView;
67 private final Object myMessageViewLock = new Object();
68 private final Project myProject;
69 private final String myContentName;
70 private final boolean myHeadlessMode;
71 private boolean myIsBackgroundMode;
72 private int myErrorCount = 0;
73 private int myWarningCount = 0;
74 private String myStatisticsText = "";
75 private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
76 private boolean myMessagesAutoActivated = false;
78 private volatile ProgressIndicator myIndicator = new EmptyProgressIndicator();
79 private Runnable myCompileWork;
80 private AtomicBoolean myMessageViewWasPrepared = new AtomicBoolean(false);
81 private Runnable myRestartWork;
83 public CompilerTask(@NotNull Project project, boolean compileInBackground, String contentName, final boolean headlessMode) {
84 super(project, contentName);
85 myProject = project;
86 myIsBackgroundMode = compileInBackground;
87 myContentName = contentName;
88 myHeadlessMode = headlessMode || IS_UNIT_TEST_MODE;
91 public String getProcessId() {
92 return ProgressFunComponentProvider.COMPILATION_ID;
95 public boolean shouldStartInBackground() {
96 return myIsBackgroundMode;
99 public ProgressIndicator getIndicator() {
100 return myIndicator;
103 @Nullable
104 public NotificationInfo getNotificationInfo() {
105 return new NotificationInfo("Compiler", "Compilation Finished", myErrorCount + " Errors, " + myWarningCount + " Warnings", true);
108 private CloseListener myCloseListener;
110 public void run(@NotNull final ProgressIndicator indicator) {
111 myIndicator = indicator;
113 final ProjectManager projectManager = ProjectManager.getInstance();
114 projectManager.addProjectManagerListener(myProject, myCloseListener = new CloseListener());
116 final Semaphore semaphore = ((CompilerManagerImpl)CompilerManager.getInstance(myProject)).getCompilationSemaphore();
117 semaphore.acquireUninterruptibly();
118 try {
119 if (!isHeadless()) {
120 addIndicatorDelegate();
122 myCompileWork.run();
124 finally {
125 indicator.stop();
126 projectManager.removeProjectManagerListener(myProject, myCloseListener);
127 semaphore.release();
131 private void prepareMessageView() {
132 if (!myIndicator.isRunning()) {
133 return;
135 if (myMessageViewWasPrepared.getAndSet(true)) {
136 return;
139 ApplicationManager.getApplication().invokeLater(new Runnable() {
140 public void run() {
141 //if (myIsBackgroundMode) {
142 // openMessageView();
143 //}
144 //else {
145 synchronized (myMessageViewLock) {
146 // clear messages from the previous compilation
147 if (myErrorTreeView == null) {
148 // if message view != null, the contents has already been cleared
149 removeAllContents(myProject, null);
157 private void addIndicatorDelegate() {
158 ((ProgressIndicatorEx)myIndicator).addStateDelegate(new ProgressIndicatorBase() {
160 public void cancel() {
161 super.cancel();
162 closeUI();
165 public void stop() {
166 super.stop();
167 if (!isCanceled()) {
168 closeUI();
172 public void setText(final String text) {
173 updateProgressText();
176 public void setText2(final String text) {
177 updateProgressText();
180 public void setFraction(final double fraction) {
181 updateProgressText();
184 protected void onProgressChange() {
185 prepareMessageView();
190 public void cancel() {
191 if (!myIndicator.isCanceled()) {
192 myIndicator.cancel();
196 public void addMessage(final CompileContext compileContext, final CompilerMessage message) {
197 prepareMessageView();
199 final CompilerMessageCategory messageCategory = message.getCategory();
200 if (CompilerMessageCategory.WARNING.equals(messageCategory)) {
201 myWarningCount += 1;
203 else if (CompilerMessageCategory.ERROR.equals(messageCategory)) {
204 myErrorCount += 1;
205 informWolf(message, compileContext);
208 if (ApplicationManager.getApplication().isDispatchThread()) {
209 openMessageView();
210 doAddMessage(message);
212 else {
213 final Window window = getWindow();
214 final ModalityState modalityState = window != null ? ModalityState.stateForComponent(window) : ModalityState.NON_MODAL;
215 ApplicationManager.getApplication().invokeLater(new Runnable() {
216 public void run() {
217 if (!myProject.isDisposed()) {
218 openMessageView();
219 doAddMessage(message);
222 }, modalityState);
226 private void informWolf(final CompilerMessage message, final CompileContext compileContext) {
227 WolfTheProblemSolver wolf = WolfTheProblemSolver.getInstance(myProject);
228 Problem problem = wolf.convertToProblem(getVirtualFile(message), convertToHighlightSeverity(message), getTextRange(message),
229 message.getMessage());
230 if (problem != null && problem.getVirtualFile() != null) {
231 final VirtualFile virtualFile = problem.getVirtualFile();
232 Document document = ApplicationManager.getApplication().runReadAction(new Computable<Document>() {
233 public Document compute() {
234 return FileDocumentManager.getInstance().getDocument(virtualFile);
238 Long compileStart = compileContext.getUserData(CompileDriver.COMPILATION_START_TIMESTAMP);
239 if (document != null && compileStart != null && compileStart.longValue() > document.getModificationStamp()) {
240 // user might have changed the file after compile start
241 wolf.weHaveGotProblem(problem);
246 private void doAddMessage(final CompilerMessage message) {
247 synchronized (myMessageViewLock) {
248 if (myErrorTreeView != null) {
249 final Navigatable navigatable = message.getNavigatable();
250 final VirtualFile file = message.getVirtualFile();
251 final CompilerMessageCategory category = message.getCategory();
252 final int type = translateCategory(category);
253 final String[] text = convertMessage(message);
254 if (navigatable != null) {
255 final String groupName = file != null? file.getPresentableUrl() : category.getPresentableText();
256 myErrorTreeView.addMessage(type, text, groupName, navigatable, message.getExportTextPrefix(), message.getRenderTextPrefix(), message.getVirtualFile());
258 else {
259 myErrorTreeView.addMessage(type, text, file, -1, -1, message.getVirtualFile());
262 final boolean shouldAutoActivate = !myMessagesAutoActivated &&
263 (CompilerMessageCategory.ERROR.equals(category) || (CompilerMessageCategory.WARNING.equals(category) && !ErrorTreeViewConfiguration.getInstance(myProject).isHideWarnings()));
264 if (shouldAutoActivate) {
265 myMessagesAutoActivated = true;
266 activateMessageView();
272 private static String[] convertMessage(final CompilerMessage message) {
273 String text = message.getMessage();
274 if (!text.contains("\n")) {
275 return new String[]{text};
277 ArrayList<String> lines = new ArrayList<String>();
278 StringTokenizer tokenizer = new StringTokenizer(text, "\n", false);
279 while (tokenizer.hasMoreTokens()) {
280 lines.add(tokenizer.nextToken());
282 return lines.toArray(new String[lines.size()]);
285 public static int translateCategory(CompilerMessageCategory category) {
286 if (CompilerMessageCategory.ERROR.equals(category)) {
287 return MessageCategory.ERROR;
289 if (CompilerMessageCategory.WARNING.equals(category)) {
290 return MessageCategory.WARNING;
292 if (CompilerMessageCategory.STATISTICS.equals(category)) {
293 return MessageCategory.STATISTICS;
295 if (CompilerMessageCategory.INFORMATION.equals(category)) {
296 return MessageCategory.INFORMATION;
298 LOG.error("Unknown message category: " + category);
299 return 0;
302 public void start(Runnable compileWork, Runnable restartWork) {
303 myCompileWork = compileWork;
304 myRestartWork = restartWork;
305 queue();
308 private void updateProgressText() {
309 if (isHeadlessMode()) {
310 return;
312 if (myAlarm.getActiveRequestCount() == 0) {
313 myAlarm.addRequest(myRepaintRequest, UPDATE_INTERVAL);
317 private Runnable myRepaintRequest = new Runnable() {
318 public void run() {
319 SwingUtilities.invokeLater(myRepaintRunnable);
321 private Runnable myRepaintRunnable = new Runnable() {
322 public void run() {
323 String s = myIndicator.getText();
324 if (myIndicator.getFraction() > 0) {
325 s += " " + (int)(myIndicator.getFraction() * 100 + 0.5) + "%";
328 synchronized (myMessageViewLock) {
329 if (myIsBackgroundMode) {
330 if (myErrorTreeView != null) {
331 //myErrorTreeView.setProgressText(s);
332 myErrorTreeView.setProgressStatistics(myStatisticsText);
335 else {
336 if (myDialog != null) {
337 myDialog.setStatusText(s);
338 myDialog.setStatisticsText(myStatisticsText);
346 public void sendToBackground() {
347 myIsBackgroundMode = true;
348 SwingUtilities.invokeLater(new Runnable() {
349 public void run() {
350 closeProgressDialog();
356 // error tree view initialization must be invoked from event dispatch thread
357 private void openMessageView() {
358 if (isHeadlessMode()) {
359 return;
361 if (myIndicator.isCanceled()) {
362 return;
365 final JComponent component;
366 synchronized (myMessageViewLock) {
367 if (myErrorTreeView != null) {
368 return;
370 myErrorTreeView = new CompilerErrorTreeView(
371 myProject,
372 myRestartWork
375 myErrorTreeView.setProcessController(new NewErrorTreeViewPanel.ProcessController() {
376 public void stopProcess() {
377 cancel();
380 public boolean isProcessStopped() {
381 return !myIndicator.isRunning();
384 component = myErrorTreeView.getComponent();
387 final MessageView messageView = myProject.getComponent(MessageView.class);
388 final Content content = PeerFactory.getInstance().getContentFactory().createContent(component, myContentName, true);
389 content.putUserData(CONTENT_ID_KEY, myContentId);
390 messageView.getContentManager().addContent(content);
391 myCloseListener.setContent(content, messageView.getContentManager());
392 removeAllContents(myProject, content);
393 messageView.getContentManager().setSelectedContent(content);
394 updateProgressText();
397 public void showCompilerContent() {
398 synchronized (myMessageViewLock) {
399 if (myErrorTreeView != null) {
400 final MessageView messageView = myProject.getComponent(MessageView.class);
401 Content[] contents = messageView.getContentManager().getContents();
402 for (Content content : contents) {
403 if (content.getUserData(CONTENT_ID_KEY) != null) {
404 messageView.getContentManager().setSelectedContent(content);
405 return;
412 public static void removeAllContents(Project project, Content notRemove) {
413 MessageView messageView = project.getComponent(MessageView.class);
414 Content[] contents = messageView.getContentManager().getContents();
415 for (Content content : contents) {
416 if (content.isPinned()) continue;
417 if (content == notRemove) continue;
418 if (content.getUserData(CONTENT_ID_KEY) != null) { // the content was added by me
419 messageView.getContentManager().removeContent(content, true);
424 private void activateMessageView() {
425 synchronized (myMessageViewLock) {
426 if (myErrorTreeView != null) {
427 ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.MESSAGES_WINDOW).activate(null);
432 private void closeUI() {
433 if (isHeadlessMode()) {
434 return;
436 Window window = getWindow();
437 ModalityState modalityState = window != null ? ModalityState.stateForComponent(window) : ModalityState.NON_MODAL;
438 final Application application = ApplicationManager.getApplication();
439 application.invokeLater(new Runnable() {
440 public void run() {
441 closeProgressDialog();
442 synchronized (myMessageViewLock) {
443 if (myErrorTreeView != null) {
444 final boolean shouldRetainView = myErrorCount > 0 || (myWarningCount > 0 && !myErrorTreeView.isHideWarnings());
445 if (shouldRetainView) {
446 addMessage(null, new CompilerMessageImpl(myProject, CompilerMessageCategory.STATISTICS,
447 CompilerBundle.message("statistics.error.count", myErrorCount), null, -1, -1, null));
448 addMessage(null, new CompilerMessageImpl(myProject, CompilerMessageCategory.STATISTICS,
449 CompilerBundle.message("statistics.warnings.count", myWarningCount), null, -1, -1, null));
450 //activateMessageView();
451 myErrorTreeView.selectFirstMessage();
453 else {
454 removeAllContents(myProject, null);
459 }, modalityState);
462 public Window getWindow(){
463 if (!myIsBackgroundMode && myDialog != null) {
464 final Container pane = myDialog.getContentPane();
465 return pane != null? SwingUtilities.windowForComponent(pane) : null;
467 return null;
470 private void closeProgressDialog() {
471 synchronized (myMessageViewLock) {
472 if (myDialog != null) {
473 myDialog.close(DialogWrapper.CANCEL_EXIT_CODE);
474 myDialog = null;
479 public boolean isHeadless() {
480 return myHeadlessMode;
483 private boolean isHeadlessMode() {
484 return myHeadlessMode;
487 private static VirtualFile getVirtualFile(final CompilerMessage message) {
488 VirtualFile virtualFile = message.getVirtualFile();
489 if (virtualFile == null) {
490 Navigatable navigatable = message.getNavigatable();
491 if (navigatable instanceof OpenFileDescriptor) {
492 virtualFile = ((OpenFileDescriptor)navigatable).getFile();
495 return virtualFile;
498 private static HighlightSeverity convertToHighlightSeverity(final CompilerMessage message) {
499 CompilerMessageCategory category = message.getCategory();
500 switch (category) {
501 case ERROR:
502 return HighlightSeverity.ERROR;
503 case WARNING:
504 return HighlightSeverity.WARNING;
505 case INFORMATION:
506 return HighlightSeverity.INFORMATION;
507 case STATISTICS:
508 return HighlightSeverity.INFORMATION;
510 return null;
513 public static TextRange getTextRange(final CompilerMessage message) {
514 Navigatable navigatable = message.getNavigatable();
515 if (navigatable instanceof OpenFileDescriptor) {
516 int offset = ((OpenFileDescriptor)navigatable).getOffset();
517 return new TextRange(offset, offset);
519 return new TextRange(0, 0);
522 private class CloseListener extends ContentManagerAdapter implements ProjectManagerListener {
523 private Content myContent;
524 private ContentManager myContentManager;
525 private boolean myIsApplicationExitingOrProjectClosing = false;
526 private boolean myUserAcceptedCancel = false;
528 public boolean canCloseProject(final Project project) {
529 assert project != null;
530 if (shouldAskUser()) {
531 int result = Messages.showOkCancelDialog(
532 myProject,
533 CompilerBundle.message("warning.compiler.running.on.project.close"),
534 CompilerBundle.message("compiler.running.dialog.title"),
535 Messages.getQuestionIcon()
537 if (result != 0) {
538 return false; // veto closing
540 myUserAcceptedCancel = true;
542 final MessageBusConnection connection = project.getMessageBus().connect();
543 connection.subscribe(CompilerTopics.COMPILATION_STATUS, new CompilationStatusListener() {
544 public void compilationFinished(boolean aborted, int errors, int warnings, final CompileContext compileContext) {
545 connection.disconnect();
546 ProjectUtil.closeProject(project);
549 cancel();
550 return false; // cancel compiler and let it finish, after compilation close the project, but currently - veto closing
552 return !myIndicator.isRunning();
555 public void setContent(Content content, ContentManager contentManager) {
556 myContent = content;
557 myContentManager = contentManager;
558 contentManager.addContentManagerListener(this);
561 public void contentRemoved(ContentManagerEvent event) {
562 if (event.getContent() == myContent) {
563 synchronized (myMessageViewLock) {
564 if (myErrorTreeView != null) {
565 myErrorTreeView.dispose();
566 myErrorTreeView = null;
567 if (myIndicator.isRunning()) {
568 cancel();
572 myContentManager.removeContentManagerListener(this);
573 myContent.release();
574 myContent = null;
578 public void contentRemoveQuery(ContentManagerEvent event) {
579 if (event.getContent() == myContent) {
580 if (!myIndicator.isCanceled() && shouldAskUser()) {
581 int result = Messages.showOkCancelDialog(
582 myProject,
583 CompilerBundle.message("warning.compiler.running.on.toolwindow.close"),
584 CompilerBundle.message("compiler.running.dialog.title"),
585 Messages.getQuestionIcon()
587 if (result != 0) {
588 event.consume(); // veto closing
590 myUserAcceptedCancel = true;
595 private boolean shouldAskUser() {
596 if (myUserAcceptedCancel) {
597 return false; // do not ask second time if user already accepted closing
599 return !myIsApplicationExitingOrProjectClosing && myIndicator.isRunning();
602 public void projectOpened(Project project) {
605 public void projectClosed(Project project) {
606 if (myContent != null) {
607 myContentManager.removeContent(myContent, true);
611 public void projectClosing(Project project) {
612 myIsApplicationExitingOrProjectClosing = true;