2 * @author: Eugene Zhuravlev
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
;
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
);
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() {
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();
120 addIndicatorDelegate();
126 projectManager
.removeProjectManagerListener(myProject
, myCloseListener
);
131 private void prepareMessageView() {
132 if (!myIndicator
.isRunning()) {
135 if (myMessageViewWasPrepared
.getAndSet(true)) {
139 ApplicationManager
.getApplication().invokeLater(new Runnable() {
141 //if (myIsBackgroundMode) {
142 // openMessageView();
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() {
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
)) {
203 else if (CompilerMessageCategory
.ERROR
.equals(messageCategory
)) {
205 informWolf(message
, compileContext
);
208 if (ApplicationManager
.getApplication().isDispatchThread()) {
210 doAddMessage(message
);
213 final Window window
= getWindow();
214 final ModalityState modalityState
= window
!= null ? ModalityState
.stateForComponent(window
) : ModalityState
.NON_MODAL
;
215 ApplicationManager
.getApplication().invokeLater(new Runnable() {
217 if (!myProject
.isDisposed()) {
219 doAddMessage(message
);
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());
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
);
302 public void start(Runnable compileWork
, Runnable restartWork
) {
303 myCompileWork
= compileWork
;
304 myRestartWork
= restartWork
;
308 private void updateProgressText() {
309 if (isHeadlessMode()) {
312 if (myAlarm
.getActiveRequestCount() == 0) {
313 myAlarm
.addRequest(myRepaintRequest
, UPDATE_INTERVAL
);
317 private Runnable myRepaintRequest
= new Runnable() {
319 SwingUtilities
.invokeLater(myRepaintRunnable
);
321 private Runnable myRepaintRunnable
= new Runnable() {
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
);
336 if (myDialog
!= null) {
337 myDialog
.setStatusText(s
);
338 myDialog
.setStatisticsText(myStatisticsText
);
346 public void sendToBackground() {
347 myIsBackgroundMode
= true;
348 SwingUtilities
.invokeLater(new Runnable() {
350 closeProgressDialog();
356 // error tree view initialization must be invoked from event dispatch thread
357 private void openMessageView() {
358 if (isHeadlessMode()) {
361 if (myIndicator
.isCanceled()) {
365 final JComponent component
;
366 synchronized (myMessageViewLock
) {
367 if (myErrorTreeView
!= null) {
370 myErrorTreeView
= new CompilerErrorTreeView(
375 myErrorTreeView
.setProcessController(new NewErrorTreeViewPanel
.ProcessController() {
376 public void stopProcess() {
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
);
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()) {
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() {
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();
454 removeAllContents(myProject
, null);
462 public Window
getWindow(){
463 if (!myIsBackgroundMode
&& myDialog
!= null) {
464 final Container pane
= myDialog
.getContentPane();
465 return pane
!= null? SwingUtilities
.windowForComponent(pane
) : null;
470 private void closeProgressDialog() {
471 synchronized (myMessageViewLock
) {
472 if (myDialog
!= null) {
473 myDialog
.close(DialogWrapper
.CANCEL_EXIT_CODE
);
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();
498 private static HighlightSeverity
convertToHighlightSeverity(final CompilerMessage message
) {
499 CompilerMessageCategory category
= message
.getCategory();
502 return HighlightSeverity
.ERROR
;
504 return HighlightSeverity
.WARNING
;
506 return HighlightSeverity
.INFORMATION
;
508 return HighlightSeverity
.INFORMATION
;
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(
533 CompilerBundle
.message("warning.compiler.running.on.project.close"),
534 CompilerBundle
.message("compiler.running.dialog.title"),
535 Messages
.getQuestionIcon()
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
);
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
) {
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()) {
572 myContentManager
.removeContentManagerListener(this);
578 public void contentRemoveQuery(ContentManagerEvent event
) {
579 if (event
.getContent() == myContent
) {
580 if (!myIndicator
.isCanceled() && shouldAskUser()) {
581 int result
= Messages
.showOkCancelDialog(
583 CompilerBundle
.message("warning.compiler.running.on.toolwindow.close"),
584 CompilerBundle
.message("compiler.running.dialog.title"),
585 Messages
.getQuestionIcon()
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;