-terminate automake session when project is disposed
[idea/community.git] / java / compiler / impl / src / com / intellij / compiler / CompileServerManager.java
1 /*
2  * Copyright 2000-2011 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.compiler;
17
18 import com.intellij.ProjectTopics;
19 import com.intellij.application.options.PathMacrosImpl;
20 import com.intellij.execution.ExecutionException;
21 import com.intellij.execution.configurations.GeneralCommandLine;
22 import com.intellij.execution.process.OSProcessHandler;
23 import com.intellij.execution.process.ProcessAdapter;
24 import com.intellij.execution.process.ProcessEvent;
25 import com.intellij.execution.process.ProcessOutputTypes;
26 import com.intellij.notification.Notification;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.application.PathMacros;
29 import com.intellij.openapi.application.PathManager;
30 import com.intellij.openapi.compiler.CompilationStatusListener;
31 import com.intellij.openapi.compiler.CompilerManager;
32 import com.intellij.openapi.compiler.CompilerTopics;
33 import com.intellij.openapi.components.ApplicationComponent;
34 import com.intellij.openapi.diagnostic.Logger;
35 import com.intellij.openapi.project.Project;
36 import com.intellij.openapi.project.ProjectManager;
37 import com.intellij.openapi.project.ProjectManagerAdapter;
38 import com.intellij.openapi.projectRoots.JavaSdkType;
39 import com.intellij.openapi.projectRoots.ProjectJdkTable;
40 import com.intellij.openapi.projectRoots.Sdk;
41 import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl;
42 import com.intellij.openapi.roots.ModuleRootEvent;
43 import com.intellij.openapi.roots.ModuleRootListener;
44 import com.intellij.openapi.roots.OrderRootType;
45 import com.intellij.openapi.roots.libraries.Library;
46 import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
47 import com.intellij.openapi.ui.MessageType;
48 import com.intellij.openapi.util.Key;
49 import com.intellij.openapi.util.Ref;
50 import com.intellij.openapi.util.ShutDownTracker;
51 import com.intellij.openapi.util.io.FileUtil;
52 import com.intellij.openapi.util.registry.Registry;
53 import com.intellij.openapi.util.text.StringUtil;
54 import com.intellij.openapi.vfs.*;
55 import com.intellij.openapi.vfs.encoding.EncodingManager;
56 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
57 import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
58 import com.intellij.problems.Problem;
59 import com.intellij.problems.WolfTheProblemSolver;
60 import com.intellij.util.Alarm;
61 import com.intellij.util.concurrency.Semaphore;
62 import com.intellij.util.messages.MessageBusConnection;
63 import com.intellij.util.net.NetUtils;
64 import org.jetbrains.annotations.NotNull;
65 import org.jetbrains.annotations.Nullable;
66 import org.jetbrains.jps.api.*;
67 import org.jetbrains.jps.client.CompileServerClient;
68 import org.jetbrains.jps.server.ClasspathBootstrap;
69 import org.jetbrains.jps.server.Server;
70
71 import javax.tools.*;
72 import java.io.File;
73 import java.io.FileOutputStream;
74 import java.io.IOException;
75 import java.io.InputStream;
76 import java.util.*;
77 import java.util.concurrent.Future;
78 import java.util.concurrent.RunnableFuture;
79 import java.util.concurrent.TimeUnit;
80 import java.util.concurrent.atomic.AtomicBoolean;
81
82 /**
83  * @author Eugene Zhuravlev
84  *         Date: 9/6/11
85  */
86 public class CompileServerManager implements ApplicationComponent{
87   private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.CompileServerManager");
88   private static final String COMPILE_SERVER_SYSTEM_ROOT = "compile-server";
89   private static final String LOGGER_CONFIG = "log.xml";
90   private static final String DEFAULT_LOGGER_CONFIG = "defaultLogConfig.xml";
91   private volatile OSProcessHandler myProcessHandler;
92   private final File mySystemDirectory;
93   private volatile CompileServerClient myClient = new CompileServerClient();
94   private final SequentialTaskExecutor myTaskExecutor = new SequentialTaskExecutor(new SequentialTaskExecutor.AsyncTaskExecutor() {
95     public void submit(Runnable runnable) {
96       ApplicationManager.getApplication().executeOnPooledThread(runnable);
97     }
98   });
99   private final ProjectManager myProjectManager;
100   private static final int MAKE_TRIGGER_DELAY = 5 * 1000 /*5 seconds*/;
101   private final Map<RequestFuture, Project> myAutomakeFutures = new HashMap<RequestFuture, Project>();
102
103   public CompileServerManager(final ProjectManager projectManager) {
104     myProjectManager = projectManager;
105     final String systemPath = PathManager.getSystemPath();
106     File system = new File(systemPath);
107     try {
108       system = system.getCanonicalFile();
109     }
110     catch (IOException e) {
111       LOG.info(e);
112     }
113     mySystemDirectory = system;
114
115     projectManager.addProjectManagerListener(new ProjectWatcher());
116     final MessageBusConnection conn = ApplicationManager.getApplication().getMessageBus().connect();
117     conn.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
118       private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
119       private final AtomicBoolean myAutoMakeInProgress = new AtomicBoolean(false);
120       @Override
121       public void before(List<? extends VFileEvent> events) {
122       }
123
124       @Override
125       public void after(List<? extends VFileEvent> events) {
126         if (shouldTriggerMake(events)) {
127           scheduleMake(new Runnable() {
128             @Override
129             public void run() {
130               if (!myAutoMakeInProgress.getAndSet(true)) {
131                 ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
132                   @Override
133                   public void run() {
134                     try {
135                       runAutoMake();
136                     }
137                     finally {
138                       myAutoMakeInProgress.set(false);
139                     }
140                   }
141                 });
142               }
143               else {
144                 scheduleMake(this);
145               }
146             }
147           });
148         }
149       }
150
151       private void scheduleMake(Runnable runnable) {
152         myAlarm.cancelAllRequests();
153         myAlarm.addRequest(runnable, MAKE_TRIGGER_DELAY);
154       }
155
156       private boolean shouldTriggerMake(List<? extends VFileEvent> events) {
157         for (VFileEvent event : events) {
158           if (event.isFromRefresh() || event.getRequestor() instanceof SavingRequestor) {
159             return true;
160           }
161         }
162         return false;
163       }
164     });
165
166     ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
167       @Override
168       public void run() {
169         shutdownServer(myClient, myProcessHandler);
170       }
171     });
172   }
173
174   public static CompileServerManager getInstance() {
175     return ApplicationManager.getApplication().getComponent(CompileServerManager.class);
176   }
177
178   public void notifyFilesChanged(Collection<String> paths) {
179     sendNotification(paths, false);
180   }
181
182   public void notifyFilesDeleted(Collection<String> paths) {
183     sendNotification(paths, true);
184   }
185
186   public void sendReloadRequest(final Project project) {
187     if (!project.isDefault() && project.isOpen()) {
188       myTaskExecutor.submit(new Runnable() {
189         @Override
190         public void run() {
191           try {
192             if (!project.isDisposed()) {
193               final CompileServerClient client = ensureServerRunningAndClientConnected(false);
194               if (client != null) {
195                 client.sendProjectReloadRequest(Collections.singletonList(project.getLocation()));
196               }
197             }
198           }
199           catch (Throwable e) {
200             LOG.info(e);
201           }
202         }
203       });
204     }
205   }
206
207   public void sendCancelBuildRequest(final UUID sessionId) {
208     myTaskExecutor.submit(new Runnable() {
209       @Override
210       public void run() {
211         try {
212           final CompileServerClient client = ensureServerRunningAndClientConnected(false);
213           if (client != null) {
214             client.sendCancelBuildRequest(sessionId);
215           }
216         }
217         catch (Throwable e) {
218           LOG.info(e);
219         }
220       }
221     });
222   }
223
224   private void sendNotification(final Collection<String> paths, final boolean isDeleted) {
225     if (paths.isEmpty()) {
226       return;
227     }
228     try {
229       final CompileServerClient client = ensureServerRunningAndClientConnected(false);
230       if (client != null) {
231         myTaskExecutor.submit(new Runnable() {
232           public void run() {
233             final Project[] openProjects = myProjectManager.getOpenProjects();
234             if (openProjects.length > 0) {
235               final Collection<String> changed, deleted;
236               if (isDeleted) {
237                 changed = Collections.emptyList();
238                 deleted = paths;
239               }
240               else {
241                 changed = paths;
242                 deleted = Collections.emptyList();
243               }
244               for (Project project : openProjects) {
245                 try {
246                   client.sendFSEvent(project.getLocation(), changed, deleted);
247                 }
248                 catch (Exception e) {
249                   LOG.info(e);
250                 }
251               }
252             }
253           }
254         });
255       }
256     }
257     catch (Throwable th) {
258       LOG.error(th); // should not happen
259     }
260   }
261
262   private void runAutoMake() {
263     final Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
264     if (openProjects.length > 0) {
265       final List<RequestFuture> futures = new ArrayList<RequestFuture>();
266       for (final Project project : openProjects) {
267         if (project.isDefault()) {
268           continue;
269         }
270         final CompilerWorkspaceConfiguration config = CompilerWorkspaceConfiguration.getInstance(project);
271         if (!config.USE_COMPILE_SERVER || !config.MAKE_PROJECT_ON_SAVE) {
272           continue;
273         }
274         final RequestFuture future = submitCompilationTask(
275           project, false, true, Collections.<String>emptyList(), Collections.<String>emptyList(), new AutoMakeResponseHandler(project)
276         );
277         if (future != null) {
278           futures.add(future);
279           synchronized (myAutomakeFutures) {
280             myAutomakeFutures.put(future, project);
281           }
282         }
283       }
284       try {
285         for (RequestFuture future : futures) {
286           try {
287             future.get();
288           }
289           catch (InterruptedException ignored) {
290           }
291           catch (java.util.concurrent.ExecutionException ignored) {
292           }
293         }
294       }
295       finally {
296         synchronized (myAutomakeFutures) {
297           myAutomakeFutures.keySet().removeAll(futures);
298         }
299       }
300     }
301   }
302
303   public void cancelAutoMakeTasks(Project project) {
304     synchronized (myAutomakeFutures) {
305       for (Map.Entry<RequestFuture, Project> entry : myAutomakeFutures.entrySet()) {
306         if (entry.getValue().equals(project)) {
307           entry.getKey().cancel(true);
308         }
309       }
310     }
311   }
312
313   @Nullable
314   public RequestFuture submitCompilationTask(final Project project, final boolean isRebuild, final boolean isMake, final Collection<String> modules, final Collection<String> paths, final JpsServerResponseHandler handler) {
315     final String projectId = project.getLocation();
316     final Ref<RequestFuture> futureRef = new Ref<RequestFuture>(null);
317     final RunnableFuture future = myTaskExecutor.submit(new Runnable() {
318       public void run() {
319         try {
320           final CompileServerClient client = ensureServerRunningAndClientConnected(true);
321           if (client != null) {
322             final RequestFuture requestFuture = isRebuild ?
323               client.sendRebuildRequest(projectId, handler) :
324               client.sendCompileRequest(isMake, projectId, modules, paths, handler);
325             futureRef.set(requestFuture);
326           }
327           else {
328             handler.sessionTerminated();
329           }
330         }
331         catch (Throwable e) {
332           try {
333             handler.handleFailure(ProtoUtil.createFailure(e.getMessage(), e));
334           }
335           finally {
336             handler.sessionTerminated();
337           }
338         }
339       }
340     });
341     try {
342       future.get();
343     }
344     catch (Throwable e) {
345       LOG.info(e);
346     }
347     return futureRef.get();
348   }
349
350   @Override
351   public void initComponent() {
352   }
353
354   @Override
355   public void disposeComponent() {
356     shutdownServer(myClient, myProcessHandler);
357   }
358
359   @NotNull
360   @Override
361   public String getComponentName() {
362     return "com.intellij.compiler.JpsServerManager";
363   }
364
365   // executed in one thread at a time
366   @Nullable
367   private CompileServerClient ensureServerRunningAndClientConnected(boolean forceRestart) throws Throwable {
368     final OSProcessHandler ph = myProcessHandler;
369     final CompileServerClient cl = myClient;
370     final boolean processNotRunning = ph == null || ph.isProcessTerminated() || ph.isProcessTerminating();
371     final boolean clientNotConnected = cl == null || !cl.isConnected();
372
373     if (processNotRunning || clientNotConnected) {
374       // cleanup; ensure the process is not running
375       shutdownServer(cl, ph);
376       myProcessHandler = null;
377       myClient = null;
378
379       if (!forceRestart) {
380         return null;
381       }
382
383       final int port = NetUtils.findAvailableSocketPort();
384       final Process process = launchServer(port);
385
386       final OSProcessHandler processHandler = new OSProcessHandler(process, null) {
387         protected boolean shouldDestroyProcessRecursively() {
388           return true;
389         }
390       };
391       final StringBuilder serverStartMessage = new StringBuilder();
392       final Semaphore semaphore  = new Semaphore();
393       semaphore.down();
394       processHandler.addProcessListener(new ProcessAdapter() {
395         public void onTextAvailable(ProcessEvent event, Key outputType) {
396           // re-translate server's output to idea.log
397           final String text = event.getText();
398           if (!StringUtil.isEmpty(text)) {
399             LOG.info("COMPILE_SERVER [" +outputType.toString() +"]: "+ text.trim());
400           }
401         }
402       });
403       processHandler.addProcessListener(new ProcessAdapter() {
404         public void processTerminated(ProcessEvent event) {
405           try {
406             processHandler.removeProcessListener(this);
407           }
408           finally {
409             semaphore.up();
410           }
411         }
412
413         public void onTextAvailable(ProcessEvent event, Key outputType) {
414           if (outputType == ProcessOutputTypes.STDERR) {
415             try {
416               final String text = event.getText();
417               if (text != null) {
418                 if (text.contains(Server.SERVER_SUCCESS_START_MESSAGE) || text.contains(Server.SERVER_ERROR_START_MESSAGE)) {
419                   processHandler.removeProcessListener(this);
420                 }
421                 if (serverStartMessage.length() > 0) {
422                   serverStartMessage.append("\n");
423                 }
424                 serverStartMessage.append(text);
425               }
426             }
427             finally {
428               semaphore.up();
429             }
430           }
431         }
432       });
433       processHandler.startNotify();
434       semaphore.waitFor();
435
436       final String startupMsg = serverStartMessage.toString();
437       if (!startupMsg.contains(Server.SERVER_SUCCESS_START_MESSAGE)) {
438         throw new Exception("Server startup failed: " + startupMsg);
439       }
440
441       CompileServerClient client = new CompileServerClient();
442       boolean connected = false;
443       try {
444         connected = client.connect(NetUtils.getLocalHostString(), port);
445         if (connected) {
446           final RequestFuture setupFuture = sendSetupRequest(client);
447           setupFuture.get();
448           myProcessHandler = processHandler;
449           myClient = client;
450         }
451       }
452       finally {
453         if (!connected) {
454           shutdownServer(cl, processHandler);
455         }
456       }
457     }
458     return myClient;
459   }
460
461   private static RequestFuture sendSetupRequest(final @NotNull CompileServerClient client) throws Exception {
462     final Map<String, String> data = new HashMap<String, String>();
463
464     // need this for tests and when this macro is missing from PathMacros registry
465     data.put(PathMacrosImpl.APPLICATION_HOME_MACRO_NAME, FileUtil.toSystemIndependentName(PathManager.getHomePath()));
466
467     final PathMacros pathVars = PathMacros.getInstance();
468     for (String name : pathVars.getAllMacroNames()) {
469       final String path = pathVars.getValue(name);
470       if (path != null) {
471         data.put(name, FileUtil.toSystemIndependentName(path));
472       }
473     }
474
475     final List<GlobalLibrary> globals = new ArrayList<GlobalLibrary>();
476
477     fillSdks(globals);
478     fillGlobalLibraries(globals);
479
480     return client.sendSetupRequest(data, globals, EncodingManager.getInstance().getDefaultCharsetName());
481   }
482
483   private static void fillSdks(List<GlobalLibrary> globals) {
484     for (Sdk sdk : ProjectJdkTable.getInstance().getAllJdks()) {
485       final String name = sdk.getName();
486       final String homePath = sdk.getHomePath();
487       if (homePath == null) {
488         continue;
489       }
490       final List<String> paths = convertToLocalPaths(sdk.getRootProvider().getFiles(OrderRootType.CLASSES));
491       globals.add(new SdkLibrary(name, homePath, paths));
492     }
493   }
494
495   private static void fillGlobalLibraries(List<GlobalLibrary> globals) {
496     final Iterator<Library> iterator = LibraryTablesRegistrar.getInstance().getLibraryTable().getLibraryIterator();
497     while (iterator.hasNext()) {
498       Library library = iterator.next();
499       final String name = library.getName();
500
501       if (name != null) {
502         final List<String> paths = convertToLocalPaths(library.getFiles(OrderRootType.CLASSES));
503         globals.add(new GlobalLibrary(name, paths));
504       }
505     }
506   }
507
508   private static List<String> convertToLocalPaths(VirtualFile[] files) {
509     final List<String> paths = new ArrayList<String>();
510     for (VirtualFile file : files) {
511       if (file.isValid()) {
512         paths.add(StringUtil.trimEnd(FileUtil.toSystemIndependentName(file.getPath()), JarFileSystem.JAR_SEPARATOR));
513       }
514     }
515     return paths;
516   }
517
518   //public static void addLocaleOptions(final List<String> commandLine, final boolean launcherUsed) {
519   //  // need to specify default encoding so that javac outputs messages in 'correct' language
520   //  commandLine.add((launcherUsed? "-J" : "") + "-D" + CharsetToolkit.FILE_ENCODING_PROPERTY + "=" + CharsetToolkit.getDefaultSystemCharset().name());
521   //}
522
523   private Process launchServer(final int port) throws ExecutionException {
524     // validate tools.jar presence
525     final JavaCompiler systemCompiler = ToolProvider.getSystemJavaCompiler();
526     if (systemCompiler == null) {
527       throw new ExecutionException("No system java compiler is provided by the JRE. Make sure tools.jar is present in IntelliJ IDEA classpath.");
528     }
529
530     final Sdk projectJdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk();
531     final GeneralCommandLine cmdLine = new GeneralCommandLine();
532     final String vmExecutablePath = ((JavaSdkType)projectJdk.getSdkType()).getVMExecutablePath(projectJdk);
533     cmdLine.setExePath(vmExecutablePath);
534     cmdLine.addParameter("-server");
535     cmdLine.addParameter("-XX:MaxPermSize=150m");
536     cmdLine.addParameter("-XX:ReservedCodeCacheSize=64m");
537     cmdLine.addParameter("-Xmx" + Registry.intValue("compiler.server.heap.size") + "m");
538     cmdLine.addParameter("-Djava.awt.headless=true");
539     //cmdLine.addParameter("-DuseJavaUtilZip");
540     final String additionalOptions = Registry.stringValue("compiler.server.vm.options");
541     if (!StringUtil.isEmpty(additionalOptions)) {
542       final StringTokenizer tokenizer = new StringTokenizer(additionalOptions, " ", false);
543       while (tokenizer.hasMoreTokens()) {
544         cmdLine.addParameter(tokenizer.nextToken());
545       }
546     }
547
548     // debugging
549     final int debugPort = Registry.intValue("compiler.server.debug.port");
550     if (debugPort > 0) {
551       cmdLine.addParameter("-XX:+HeapDumpOnOutOfMemoryError");
552       cmdLine.addParameter("-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=" + debugPort);
553     }
554
555     if (Registry.is("compiler.server.use.memory.temp.cache")) {
556       cmdLine.addParameter("-D"+ GlobalOptions.USE_MEMORY_TEMP_CACHE_OPTION + "=true");
557     }
558     if (Registry.is("compiler.server.use.external.javac.process")) {
559       cmdLine.addParameter("-D"+ GlobalOptions.USE_EXTERNAL_JAVAC_OPTION + "=true");
560     }
561     cmdLine.addParameter("-D"+ GlobalOptions.HOSTNAME_OPTION + "=" + NetUtils.getLocalHostString());
562     cmdLine.addParameter("-D"+ GlobalOptions.VM_EXE_PATH_OPTION + "=" + FileUtil.toSystemIndependentName(vmExecutablePath));
563
564     // javac's VM should use the same default locale that IDEA uses in order for javac to print messages in 'correct' language
565     final String lang = System.getProperty("user.language");
566     if (lang != null) {
567       //noinspection HardCodedStringLiteral
568       cmdLine.addParameter("-Duser.language=" + lang);
569     }
570     final String country = System.getProperty("user.country");
571     if (country != null) {
572       //noinspection HardCodedStringLiteral
573       cmdLine.addParameter("-Duser.country=" + country);
574     }
575     //noinspection HardCodedStringLiteral
576     final String region = System.getProperty("user.region");
577     if (region != null) {
578       //noinspection HardCodedStringLiteral
579       cmdLine.addParameter("-Duser.region=" + region);
580     }
581
582     cmdLine.addParameter("-classpath");
583
584     final List<File> cp = ClasspathBootstrap.getCompileServerApplicationClasspath();
585
586     cmdLine.addParameter(classpathToString(cp));
587
588     cmdLine.addParameter(org.jetbrains.jps.server.Server.class.getName());
589     cmdLine.addParameter(Integer.toString(port));
590
591     final File workDirectory = new File(mySystemDirectory, COMPILE_SERVER_SYSTEM_ROOT);
592     workDirectory.mkdirs();
593     ensureLogConfigExists(workDirectory);
594
595     cmdLine.addParameter(FileUtil.toSystemIndependentName(workDirectory.getPath()));
596
597     cmdLine.setWorkDirectory(workDirectory);
598
599
600     return cmdLine.createProcess();
601   }
602
603   private static void ensureLogConfigExists(File workDirectory) {
604     final File logConfig = new File(workDirectory, LOGGER_CONFIG);
605     if (!logConfig.exists()) {
606       FileUtil.createIfDoesntExist(logConfig);
607       try {
608         final InputStream in = Server.class.getResourceAsStream("/" + DEFAULT_LOGGER_CONFIG);
609         if (in != null) {
610           try {
611             final FileOutputStream out = new FileOutputStream(logConfig);
612             try {
613               FileUtil.copy(in, out);
614             }
615             finally {
616               out.close();
617             }
618           }
619           finally {
620             in.close();
621           }
622         }
623       }
624       catch (IOException e) {
625         LOG.error(e);
626       }
627     }
628   }
629
630   public void shutdownServer() {
631     shutdownServer(myClient, myProcessHandler);
632   }
633
634   private static void shutdownServer(final CompileServerClient client, final OSProcessHandler processHandler) {
635     try {
636       if (client != null && client.isConnected()) {
637         final Future future = client.sendShutdownRequest();
638         future.get(500, TimeUnit.MILLISECONDS);
639         client.disconnect();
640       }
641     }
642     catch (Throwable ignored) {
643       LOG.info(ignored);
644     }
645     finally {
646       if (processHandler != null) {
647         processHandler.destroyProcess();
648       }
649     }
650   }
651
652   private static String classpathToString(List<File> cp) {
653     StringBuilder builder = new StringBuilder();
654     for (File file : cp) {
655       if (builder.length() > 0) {
656         builder.append(File.pathSeparator);
657       }
658       builder.append(file.getAbsolutePath());
659     }
660     return builder.toString();
661   }
662
663   private static class AutoMakeResponseHandler extends JpsServerResponseHandler {
664     private JpsRemoteProto.Message.Response.BuildEvent.Status myBuildStatus;
665     private final Project myProject;
666     private final WolfTheProblemSolver myWolf;
667
668     public AutoMakeResponseHandler(Project project) {
669       myProject = project;
670       myBuildStatus = JpsRemoteProto.Message.Response.BuildEvent.Status.SUCCESS;
671       myWolf = WolfTheProblemSolver.getInstance(project);
672     }
673
674     @Override
675     public boolean handleBuildEvent(JpsRemoteProto.Message.Response.BuildEvent event) {
676       if (myProject.isDisposed()) {
677         return true;
678       }
679       switch (event.getEventType()) {
680         case BUILD_COMPLETED:
681           if (event.hasCompletionStatus()) {
682             myBuildStatus = event.getCompletionStatus();
683           }
684           return true;
685
686         case FILES_GENERATED:
687           final CompilationStatusListener publisher = myProject.getMessageBus().syncPublisher(CompilerTopics.COMPILATION_STATUS);
688           for (JpsRemoteProto.Message.Response.BuildEvent.GeneratedFile generatedFile : event.getGeneratedFilesList()) {
689             final String root = FileUtil.toSystemIndependentName(generatedFile.getOutputRoot());
690             final String relativePath = FileUtil.toSystemIndependentName(generatedFile.getRelativePath());
691             publisher.fileGenerated(root, relativePath);
692           }
693           return false;
694
695         default:
696           return false;
697       }
698     }
699
700     @Override
701     public void handleCompileMessage(JpsRemoteProto.Message.Response.CompileMessage compileResponse) {
702       if (myProject.isDisposed()) {
703         return;
704       }
705       final JpsRemoteProto.Message.Response.CompileMessage.Kind kind = compileResponse.getKind();
706       if (kind == JpsRemoteProto.Message.Response.CompileMessage.Kind.ERROR) {
707         informWolf(myProject, compileResponse);
708       }
709     }
710
711     @Override
712     public void handleFailure(JpsRemoteProto.Message.Failure failure) {
713       CompilerManager.NOTIFICATION_GROUP.createNotification("Auto make failure: " + failure.getDescription(), MessageType.INFO);
714     }
715
716     @Override
717     public void sessionTerminated() {
718       String statusMessage = null/*"Auto make completed"*/;
719       switch (myBuildStatus) {
720         case SUCCESS:
721           //statusMessage = "Auto make completed successfully";
722           break;
723         case UP_TO_DATE:
724           //statusMessage = "All files are up-to-date";
725           break;
726         case ERRORS:
727           statusMessage = "Auto make completed with errors";
728           break;
729         case CANCELED:
730           statusMessage = "Auto make has been canceled";
731           break;
732       }
733       if (statusMessage != null) {
734         final Notification notification = CompilerManager.NOTIFICATION_GROUP.createNotification(statusMessage, MessageType.INFO);
735         if (!myProject.isDisposed()) {
736           notification.notify(myProject);
737         }
738       }
739     }
740
741     private void informWolf(Project project, JpsRemoteProto.Message.Response.CompileMessage message) {
742       final String srcPath = message.getSourceFilePath();
743       if (srcPath != null && !project.isDisposed()) {
744         final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(srcPath);
745         if (vFile != null) {
746           final int line = (int)message.getLine();
747           final int column = (int)message.getColumn();
748           if (line > 0 && column > 0) {
749             final Problem problem = myWolf.convertToProblem(vFile, line, column, new String[]{message.getText()});
750             myWolf.weHaveGotProblems(vFile, Collections.singletonList(problem));
751           }
752           else {
753             myWolf.queue(vFile);
754           }
755         }
756       }
757     }
758   }
759
760   private class ProjectWatcher extends ProjectManagerAdapter {
761     private final Map<Project, MessageBusConnection> myConnections = new HashMap<Project, MessageBusConnection>();
762
763     public void projectOpened(final Project project) {
764       final MessageBusConnection conn = project.getMessageBus().connect();
765       myConnections.put(project, conn);
766       conn.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() {
767         public void beforeRootsChange(final ModuleRootEvent event) {
768           sendReloadRequest(project);
769         }
770
771         public void rootsChanged(final ModuleRootEvent event) {
772           myTaskExecutor.submit(new Runnable() {
773             public void run() {
774               try {
775                 // this will reload sdks and global libraries
776                 final CompileServerClient client = ensureServerRunningAndClientConnected(false);
777                 if (client != null) {
778                   sendSetupRequest(client);
779                 }
780               }
781               catch (Throwable e) {
782                 LOG.info(e);
783               }
784             }
785           });
786         }
787       });
788     }
789
790     public void projectClosing(Project project) {
791       sendReloadRequest(project);
792     }
793
794     public void projectClosed(Project project) {
795       final MessageBusConnection conn = myConnections.remove(project);
796       if (conn != null) {
797         conn.disconnect();
798       }
799     }
800   }
801 }